Commit ac41b3d9 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'merge_ce' into 'master'

Merge CE into EE

See merge request !302
parents c6f7f09d a538dc26
...@@ -3,10 +3,10 @@ Note: The upcoming release contains empty lines to reduce the number of merge co ...@@ -3,10 +3,10 @@ Note: The upcoming release contains empty lines to reduce the number of merge co
v 7.8.0 v 7.8.0
- Replace highlight.js with rouge-fork rugments (Stefan Tatschner) - Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
- Make project search case insensitive (Hannes Rosenögger) - Make project search case insensitive (Hannes Rosenögger)
- - Include issue/mr participants in list of recipients for reassign/close/reopen emails
- Expose description in groups API - Expose description in groups API
- - Better UI for project services page
- - Cleaner UI for web editor
- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger) - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
- -
- -
...@@ -15,28 +15,31 @@ v 7.8.0 ...@@ -15,28 +15,31 @@ v 7.8.0
- -
- -
- Show tags in commit view (Hannes Rosenögger) - Show tags in commit view (Hannes Rosenögger)
- Only count a user's vote once on a merge request or issue (Michael Clarke)
- -
- Increate font size when browse source files and diffs
- Create new file in empty repository using GitLab UI
- -
- Ability to clone project using oauth2 token
- -
- - Upgrade Sidekiq gem to version 3.3.0
- - Stop git zombie creation during force push check
- - Show success/error messages for test setting button in services
-
- -
- Fix commits pagination - Fix commits pagination
- -
- -
- -
- -
- Add a commit calendar to the user profile (Hannes Rosenögger)
- -
- -
- -
- Fix long broadcast message cut-off on left sidebar (Visay Keo)
- Add Project Avatars (Steven Thonus and Hannes Rosenögger)
- -
- -
- - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
-
-
-
- -
- -
- -
...@@ -47,16 +50,23 @@ v 7.8.0 ...@@ -47,16 +50,23 @@ v 7.8.0
- -
- -
- -
- Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
- -
- -
- -
- -
- -
- -
- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
- -
- -
- -
- - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
v 7.7.1
- Improve mention autocomplete performance
- Show setup instructions for GitHub import if disabled
- Allow use http for OAuth applications
v 7.7.0 v 7.7.0
- Import from GitHub.com feature - Import from GitHub.com feature
......
...@@ -119,8 +119,8 @@ gem "acts-as-taggable-on" ...@@ -119,8 +119,8 @@ gem "acts-as-taggable-on"
# Background jobs # Background jobs
gem 'slim' gem 'slim'
gem 'sinatra', require: nil gem 'sinatra', require: nil
gem 'sidekiq', '2.17.8' gem 'sidetiq', '0.6.3'
gem 'sidetiq', '0.6.1' gem 'sidekiq', '~> 3.3'
# HTTP requests # HTTP requests
gem "httparty" gem "httparty"
...@@ -156,6 +156,9 @@ gem "slack-notifier", "~> 1.0.0" ...@@ -156,6 +156,9 @@ gem "slack-notifier", "~> 1.0.0"
# d3 # d3
gem "d3_rails", "~> 3.1.4" gem "d3_rails", "~> 3.1.4"
#cal-heatmap
gem "cal-heatmap-rails", "~> 0.0.1"
# underscore-rails # underscore-rails
gem "underscore-rails", "~> 1.4.4" gem "underscore-rails", "~> 1.4.4"
...@@ -172,7 +175,7 @@ gem 'ace-rails-ap' ...@@ -172,7 +175,7 @@ gem 'ace-rails-ap'
gem 'mousetrap-rails' gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar # Semantic UI Sass for Sidebar
gem 'semantic-ui-sass', '~> 0.16.1.0' gem 'semantic-ui-sass', '~> 1.8.0'
gem "sass-rails", '~> 4.0.2' gem "sass-rails", '~> 4.0.2'
gem "coffee-rails" gem "coffee-rails"
......
...@@ -52,6 +52,7 @@ GEM ...@@ -52,6 +52,7 @@ GEM
sass (~> 3.2) sass (~> 3.2)
browser (0.7.2) browser (0.7.2)
builder (3.2.2) builder (3.2.2)
cal-heatmap-rails (0.0.1)
capybara (2.2.1) capybara (2.2.1)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
...@@ -62,8 +63,8 @@ GEM ...@@ -62,8 +63,8 @@ GEM
activemodel (>= 3.2.0) activemodel (>= 3.2.0)
activesupport (>= 3.2.0) activesupport (>= 3.2.0)
json (>= 1.7) json (>= 1.7)
celluloid (0.15.2) celluloid (0.16.0)
timers (~> 1.1.0) timers (~> 4.0.0)
charlock_holmes (0.6.9.4) charlock_holmes (0.6.9.4)
cliver (0.3.2) cliver (0.3.2)
code_analyzer (0.4.3) code_analyzer (0.4.3)
...@@ -244,6 +245,7 @@ GEM ...@@ -244,6 +245,7 @@ GEM
hike (1.2.3) hike (1.2.3)
hipchat (1.4.0) hipchat (1.4.0)
httparty httparty
hitimes (1.2.2)
html-pipeline (1.11.0) html-pipeline (1.11.0)
activesupport (>= 2) activesupport (>= 2)
nokogiri (~> 1.4) nokogiri (~> 1.4)
...@@ -259,7 +261,7 @@ GEM ...@@ -259,7 +261,7 @@ GEM
httpauth (0.2.1) httpauth (0.2.1)
httpclient (2.5.3.3) httpclient (2.5.3.3)
i18n (0.6.11) i18n (0.6.11)
ice_cube (0.12.1) ice_cube (0.11.1)
ice_nine (0.10.0) ice_nine (0.10.0)
jasmine (2.0.2) jasmine (2.0.2)
jasmine-core (~> 2.0.0) jasmine-core (~> 2.0.0)
...@@ -490,22 +492,22 @@ GEM ...@@ -490,22 +492,22 @@ GEM
activesupport (>= 3.1, < 4.2) activesupport (>= 3.1, < 4.2)
select2-rails (3.5.2) select2-rails (3.5.2)
thor (~> 0.14) thor (~> 0.14)
semantic-ui-sass (0.16.1.0) semantic-ui-sass (1.8.0.0)
sass (~> 3.2) sass (~> 3.2)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.4.0) sexp_processor (4.4.0)
shoulda-matchers (2.1.0) shoulda-matchers (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (2.17.8) sidekiq (3.3.0)
celluloid (= 0.15.2) celluloid (>= 0.16.0)
connection_pool (~> 2.0) connection_pool (>= 2.0.0)
json json
redis (~> 3.1) redis (>= 3.0.6)
redis-namespace (~> 1.3) redis-namespace (>= 1.3.1)
sidetiq (0.6.1) sidetiq (0.6.3)
celluloid (>= 0.14.1) celluloid (>= 0.14.1)
ice_cube (~> 0.12.0) ice_cube (= 0.11.1)
sidekiq (>= 2.16.0) sidekiq (>= 3.0.0)
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.9.0) simplecov (0.9.0)
docile (~> 1.1.0) docile (~> 1.1.0)
...@@ -560,7 +562,8 @@ GEM ...@@ -560,7 +562,8 @@ GEM
thor (0.19.1) thor (0.19.1)
thread_safe (0.3.4) thread_safe (0.3.4)
tilt (1.4.1) tilt (1.4.1)
timers (1.1.0) timers (4.0.1)
hitimes
timfel-krb5-auth (0.8) timfel-krb5-auth (0.8)
tinder (1.9.3) tinder (1.9.3)
eventmachine (~> 1.0) eventmachine (~> 1.0)
...@@ -630,6 +633,7 @@ DEPENDENCIES ...@@ -630,6 +633,7 @@ DEPENDENCIES
binding_of_caller binding_of_caller
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
browser browser
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.2.1) capybara (~> 2.2.1)
carrierwave carrierwave
coffee-rails coffee-rails
...@@ -719,11 +723,11 @@ DEPENDENCIES ...@@ -719,11 +723,11 @@ DEPENDENCIES
sdoc sdoc
seed-fu seed-fu
select2-rails select2-rails
semantic-ui-sass (~> 0.16.1.0) semantic-ui-sass (~> 1.8.0)
settingslogic settingslogic
shoulda-matchers (~> 2.1.0) shoulda-matchers (~> 2.1.0)
sidekiq (= 2.17.8) sidekiq (~> 3.3)
sidetiq (= 0.6.1) sidetiq (= 0.6.3)
simplecov simplecov
sinatra sinatra
six six
......
app/assets/images/bg-header.png

210 Bytes | W: | H:

app/assets/images/bg-header.png

90 Bytes | W: | H:

app/assets/images/bg-header.png
app/assets/images/bg-header.png
app/assets/images/bg-header.png
app/assets/images/bg-header.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/bg_fallback.png

2.91 KB | W: | H:

app/assets/images/bg_fallback.png

167 Bytes | W: | H:

app/assets/images/bg_fallback.png
app/assets/images/bg_fallback.png
app/assets/images/bg_fallback.png
app/assets/images/bg_fallback.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/brand_logo.png

31.4 KB | W: | H:

app/assets/images/brand_logo.png

26.4 KB | W: | H:

app/assets/images/brand_logo.png
app/assets/images/brand_logo.png
app/assets/images/brand_logo.png
app/assets/images/brand_logo.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/chosen-sprite.png

396 Bytes | W: | H:

app/assets/images/chosen-sprite.png

367 Bytes | W: | H:

app/assets/images/chosen-sprite.png
app/assets/images/chosen-sprite.png
app/assets/images/chosen-sprite.png
app/assets/images/chosen-sprite.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/diff_note_add.png

691 Bytes | W: | H:

app/assets/images/diff_note_add.png

418 Bytes | W: | H:

app/assets/images/diff_note_add.png
app/assets/images/diff_note_add.png
app/assets/images/diff_note_add.png
app/assets/images/diff_note_add.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/icon-link.png

1019 Bytes | W: | H:

app/assets/images/icon-link.png

726 Bytes | W: | H:

app/assets/images/icon-link.png
app/assets/images/icon-link.png
app/assets/images/icon-link.png
app/assets/images/icon-link.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/icon-search.png

331 Bytes | W: | H:

app/assets/images/icon-search.png

222 Bytes | W: | H:

app/assets/images/icon-search.png
app/assets/images/icon-search.png
app/assets/images/icon-search.png
app/assets/images/icon-search.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/icon_sprite.png

2.72 KB | W: | H:

app/assets/images/icon_sprite.png

2.57 KB | W: | H:

app/assets/images/icon_sprite.png
app/assets/images/icon_sprite.png
app/assets/images/icon_sprite.png
app/assets/images/icon_sprite.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/images.png

6.49 KB | W: | H:

app/assets/images/images.png

5.71 KB | W: | H:

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

2.73 KB | W: | H:

app/assets/images/logo-black.png

2.55 KB | W: | H:

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

7.33 KB | W: | H:

app/assets/images/logo-white.png

7.16 KB | W: | H:

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

260 Bytes | W: | H:

app/assets/images/move.png

197 Bytes | W: | H:

app/assets/images/move.png
app/assets/images/move.png
app/assets/images/move.png
app/assets/images/move.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/no_avatar.png

704 Bytes | W: | H:

app/assets/images/no_avatar.png

621 Bytes | W: | H:

app/assets/images/no_avatar.png
app/assets/images/no_avatar.png
app/assets/images/no_avatar.png
app/assets/images/no_avatar.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/no_group_avatar.png

4.77 KB | W: | H:

app/assets/images/no_group_avatar.png

942 Bytes | W: | H:

app/assets/images/no_group_avatar.png
app/assets/images/no_group_avatar.png
app/assets/images/no_group_avatar.png
app/assets/images/no_group_avatar.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/slider_handles.png

4.03 KB | W: | H:

app/assets/images/slider_handles.png

1.34 KB | W: | H:

app/assets/images/slider_handles.png
app/assets/images/slider_handles.png
app/assets/images/slider_handles.png
app/assets/images/slider_handles.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/switch_icon.png

1.17 KB | W: | H:

app/assets/images/switch_icon.png

231 Bytes | W: | H:

app/assets/images/switch_icon.png
app/assets/images/switch_icon.png
app/assets/images/switch_icon.png
app/assets/images/switch_icon.png
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/trans_bg.gif

50 Bytes | W: | H:

app/assets/images/trans_bg.gif

49 Bytes | W: | H:

app/assets/images/trans_bg.gif
app/assets/images/trans_bg.gif
app/assets/images/trans_bg.gif
app/assets/images/trans_bg.gif
  • 2-up
  • Swipe
  • Onion skin
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#= require shortcuts_dashboard_navigation #= require shortcuts_dashboard_navigation
#= require shortcuts_issueable #= require shortcuts_issueable
#= require shortcuts_network #= require shortcuts_network
#= require cal-heatmap
#= require_tree . #= require_tree .
window.slugify = (text) -> window.slugify = (text) ->
...@@ -74,24 +75,18 @@ window.disableButtonIfEmptyField = (field_selector, button_selector) -> ...@@ -74,24 +75,18 @@ window.disableButtonIfEmptyField = (field_selector, button_selector) ->
# Disable button if any input field with given selector is empty # Disable button if any input field with given selector is empty
window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) -> window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
closest_submit = form.find(button_selector) closest_submit = form.find(button_selector)
empty = false updateButtons = ->
filled = true
form.find('input').filter(form_selector).each -> form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is "" filled = rstrip($(this).val()) != "" || !$(this).attr('required')
if empty if filled
closest_submit.disable()
else
closest_submit.enable() closest_submit.enable()
form.keyup ->
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
if empty
closest_submit.disable()
else else
closest_submit.enable() closest_submit.disable()
updateButtons()
form.keyup(updateButtons)
window.sanitize = (str) -> window.sanitize = (str) ->
return str.replace(/<(?:.|\n)*?>/gm, '') return str.replace(/<(?:.|\n)*?>/gm, '')
......
class @EditBlob
constructor: (assets_path, mode)->
ace.config.set "modePath", assets_path + '/ace'
ace.config.loadModule "ace/ext/searchbox"
if mode
ace_mode = mode
editor = ace.edit("editor")
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click ->
$("#file-content").val editor.getValue()
$(".file-editor form").submit()
return
editModePanes = $(".js-edit-mode-pane")
editModeLinks = $(".js-edit-mode a")
editModeLinks.click (event) ->
event.preventDefault()
currentLink = $(this)
paneId = currentLink.attr("href")
currentPane = editModePanes.filter(paneId)
editModeLinks.parent().removeClass "active hover"
currentLink.parent().addClass "active hover"
editModePanes.hide()
if paneId is "#preview"
currentPane.fadeIn 200
$.post currentLink.data("preview-url"),
content: editor.getValue()
, (response) ->
currentPane.empty().append response
return
else
currentPane.fadeIn 200
editor.focus()
return
editor: ->
return @editor
class @NewBlob
constructor: (assets_path, mode)->
ace.config.set "modePath", assets_path + '/ace'
ace.config.loadModule "ace/ext/searchbox"
if mode
ace_mode = mode
editor = ace.edit("editor")
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click ->
$("#file-content").val editor.getValue()
$(".file-editor form").submit()
return
editor: ->
return @editor
class @calendar
options =
month: "short"
day: "numeric"
year: "numeric"
constructor: (timestamps, starting_year, starting_month) ->
cal = new CalHeatMap()
cal.init
itemName: ["commit"]
data: timestamps
domain: "year"
subDomain: "month"
start: new Date(starting_year, starting_month)
domainLabelFormat: "%b"
id: "cal-heatmap"
domain: "month"
subDomain: "day"
range: 12
tooltip: true
domainDynamicDimension: false
colLimit: 4
label:
position: "top"
domainMargin: 1
legend: [
0
1
4
7
]
legendCellPadding: 3
onClick: (date, count) ->
return
return
...@@ -100,6 +100,7 @@ class Dispatcher ...@@ -100,6 +100,7 @@ class Dispatcher
new Profile() new Profile()
when 'projects' when 'projects'
new Project() new Project()
new ProjectAvatar()
switch path[1] switch path[1]
when 'edit' when 'edit'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
......
class @ProjectAvatar
constructor: ->
$('.js-choose-project-avatar-button').bind 'click', ->
form = $(this).closest('form')
form.find('.js-project-avatar-input').click()
$('.js-project-avatar-input').bind 'change', ->
form = $(this).closest('form')
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find('.js-avatar-filename').text(filename)
...@@ -10,20 +10,6 @@ class @ProjectNew ...@@ -10,20 +10,6 @@ class @ProjectNew
initEvents: -> initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit' disableButtonIfEmptyField '#project_name', '.project-submit'
$('#project_issues_enabled').change ->
if ($(this).is(':checked') == true)
$('#project_issues_tracker').removeAttr('disabled')
else
$('#project_issues_tracker').attr('disabled', 'disabled')
$('#project_issues_tracker').change()
$('#project_issues_tracker').change ->
if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
$('#project_issues_tracker_id').attr('disabled', 'disabled')
else
$('#project_issues_tracker_id').removeAttr('disabled')
$("#project_merge_requests_enabled").change -> $("#project_merge_requests_enabled").change ->
checked = $(this).prop("checked") checked = $(this).prop("checked")
$("#project_merge_requests_template").prop "disabled", not checked $("#project_merge_requests_template").prop "disabled", not checked
......
...@@ -6,7 +6,7 @@ class @ProjectShow ...@@ -6,7 +6,7 @@ class @ProjectShow
new Flash('Star toggle failed. Try again later.', 'alert') new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) -> $("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href") $.cookie "default_view", $(e.target).attr("href"), { expires: 30 }
defaultView = $.cookie("default_view") defaultView = $.cookie("default_view")
if defaultView if defaultView
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
*= require select2 *= require select2
*= require_self *= require_self
*= require dropzone/basic *= require dropzone/basic
*= require cal-heatmap
*/ */
@import "main/*"; @import "main/*";
......
...@@ -2,15 +2,17 @@ ...@@ -2,15 +2,17 @@
float: left; float: left;
margin-right: 12px; margin-right: 12px;
width: 40px; width: 40px;
padding: 1px; height: 40px;
@include border-radius(4px); padding: 0;
@include border-radius($avatar_radius);
&.avatar-inline { &.avatar-inline {
float: none; float: none;
margin-left: 3px; margin-left: 4px;
margin-bottom: 2px;
&.s16 { margin-right: 2px; } &.s16 { margin-right: 4px; }
&.s24 { margin-right: 2px; } &.s24 { margin-right: 4px; }
} }
&.s16 { width: 16px; height: 16px; margin-right: 6px; } &.s16 { width: 16px; height: 16px; margin-right: 6px; }
...@@ -21,3 +23,16 @@ ...@@ -21,3 +23,16 @@
&.s90 { width: 90px; height: 90px; margin-right: 15px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; } &.s160 { width: 160px; height: 160px; margin-right: 20px; }
} }
.identicon {
text-align: center;
vertical-align: top;
&.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; }
&.s32 { font-size: 24px; line-height: 1.33; }
&.s60 { font-size: 45px; line-height: 1.33; }
&.s90 { font-size: 68px; line-height: 1.33; }
&.s160 { font-size: 120px; line-height: 1.33; }
}
...@@ -173,6 +173,11 @@ ...@@ -173,6 +173,11 @@
margin-right: 0px; margin-right: 0px;
} }
} }
&.btn-lg {
font-size: 15px;
line-height: 1.4;
}
} }
.btn-block { .btn-block {
......
.calendar_onclick_placeholder {
padding: 0 0 2px 0;
}
.calendar_commit_activity {
padding: 5px 0 0;
}
.calendar_onclick_second {
font-size: 14px;
display: block;
}
.calendar_onclick_hr {
padding: 0;
margin: 10px 0;
}
.calendar_commit_date {
color: #999;
}
.calendar_activity_summary {
font-size: 14px;
}
/**
* This overwrites the default values of the cal-heatmap gem
*/
.calendar {
.qi {
background-color: #999;
fill: #fff;
}
.q1 {
background-color: #dae289;
fill: #ededed;
}
.q2 {
background-color: #cedb9c;
fill: #ACD5F2;
}
.q3 {
background-color: #b5cf6b;
fill: #7FA8D1;
}
.q4 {
background-color: #637939;
fill: #49729B;
}
.q5 {
background-color: #3b6427;
fill: #254E77;
}
.domain-background {
fill: none;
shape-rendering: crispedges;
}
.ch-tooltip {
position: absolute;
display: none;
margin-top: 22px;
margin-left: 1px;
font-size: 13px;
padding: 3px;
font-weight: 550;
background-color: #222;
span {
position: absolute;
width: 200px;
text-align: center;
visibility: hidden;
border-radius: 10px;
&:after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -8px;
width: 0;
height: 0;
border-top: 8px solid #000000;
border-right: 8px solid transparent;
border-left: 8px solid transparent;
}
}
}
}
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
border: none; border: none;
border-radius: 0; border-radius: 0;
font-family: $monospace_font; font-family: $monospace_font;
font-size: 12px !important; font-size: $code_font_size !important;
line-height: 16px !important; line-height: $code_line_height !important;
margin: 0; margin: 0;
overflow: auto; overflow: auto;
overflow-y: hidden; overflow-y: hidden;
...@@ -38,8 +38,8 @@ ...@@ -38,8 +38,8 @@
a { a {
font-family: $monospace_font; font-family: $monospace_font;
display: block; display: block;
font-size: 12px !important; font-size: $code_font_size !important;
line-height: 16px !important; line-height: $code_line_height !important;
white-space: nowrap; white-space: nowrap;
i { i {
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
background: #fff; background: #fff;
color: #737881; color: #737881;
float: left; float: left;
@include border-radius(40px); @include border-radius($avatar_radius);
@include box-shadow(0 0 0 3px #EEE); @include box-shadow(0 0 0 3px #EEE);
overflow: hidden; overflow: hidden;
...@@ -58,6 +58,10 @@ ...@@ -58,6 +58,10 @@
padding: 10px 15px; padding: 10px 15px;
margin-left: 60px; margin-left: 60px;
img {
max-width: 100%;
}
&:after { &:after {
content: ''; content: '';
display: block; display: block;
......
...@@ -128,3 +128,7 @@ a:focus { ...@@ -128,3 +128,7 @@ a:focus {
textarea.js-gfm-input { textarea.js-gfm-input {
font-family: $monospace_font; font-family: $monospace_font;
} }
.strikethrough {
text-decoration: line-through;
}
\ No newline at end of file
/* /*
* Twitter bootstrap with GitLab customizations/additions * Twitter bootstrap with GitLab customizations/additions
* *
* Some unused bootstrap compontents like panels are not included.
* Other components like tabs are modified to GitLab style.
*
*/ */
$font-size-base: 13px !default; $font-size-base: 13px !default;
......
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
} }
@mixin panel-colored { @mixin panel-colored {
border: none; border: 1px solid #EEE;
background: $box_bg; background: $box_bg;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* General Colors * General Colors
*/ */
$style_color: #474D57; $style_color: #474D57;
$hover: #FFECDB; $hover: #FFF3EB;
$box_bg: #F9F9F9; $box_bg: #F9F9F9;
/* /*
...@@ -57,3 +57,7 @@ $list-font-size: 15px; ...@@ -57,3 +57,7 @@ $list-font-size: 15px;
* Sidebar navigation width * Sidebar navigation width
*/ */
$sidebar_width: 230px; $sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
...@@ -101,7 +101,6 @@ ...@@ -101,7 +101,6 @@
.commit-title { .commit-title {
margin: 0; margin: 0;
font-size: 20px;
} }
.commit-description { .commit-description {
......
...@@ -75,6 +75,9 @@ ...@@ -75,6 +75,9 @@
} }
} }
} }
.project-avatar {
float: left;
}
.project-description { .project-description {
overflow: hidden; overflow: hidden;
...@@ -92,8 +95,24 @@ ...@@ -92,8 +95,24 @@
} }
} }
.dash-project-avatar {
float: left;
}
.dash-project-access-icon { .dash-project-access-icon {
float: left; float: left;
margin-right: 3px; margin-right: 5px;
width: 16px; width: 16px;
} }
.dash-new-project {
background: $bg_success;
border: 1px solid $border_success;
a {
color: #FFF;
}
}
.dash-list .str-truncated {
max-width: 72%;
}
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
overflow-y: hidden; overflow-y: hidden;
background: #FFF; background: #FFF;
color: #333; color: #333;
font-size: 12px; font-size: $code_font_size;
.old { .old {
span.idiff { span.idiff {
background-color: #F99; background-color: #F99;
...@@ -64,8 +64,8 @@ ...@@ -64,8 +64,8 @@
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
td { td {
line-height: 18px; line-height: $code_line_height;
font-size: 12px; font-size: $code_font_size;
} }
} }
......
...@@ -31,4 +31,26 @@ ...@@ -31,4 +31,26 @@
margin: 5px 8px 0 8px; margin: 5px 8px 0 8px;
} }
} }
.file-title {
@extend .monospace;
font-size: 14px;
padding: 5px;
}
.editor-ref {
background: #f5f5f5;
padding: 11px 15px;
border-right: 1px solid #CCC;
display: inline-block;
margin: -5px -5px;
margin-right: 10px;
}
.editor-file-name {
.new-file-name {
display: inline-block;
width: 200px;
}
}
} }
...@@ -138,9 +138,10 @@ header { ...@@ -138,9 +138,10 @@ header {
top: -1px; top: -1px;
padding-right: 0px !important; padding-right: 0px !important;
img { img {
width: 26px; width: 50px;
height: 26px; height: 50px;
@include border-radius(4px); margin: -15px;
margin-left: 5px;
} }
} }
......
...@@ -163,8 +163,9 @@ form.edit-issue { ...@@ -163,8 +163,9 @@ form.edit-issue {
} }
} }
.issue-title { h3.issue-title {
margin-top: 0; margin-top: 0;
font-size: 2em;
} }
.context .select2-container { .context .select2-container {
......
...@@ -122,6 +122,7 @@ ...@@ -122,6 +122,7 @@
background: $box_bg; background: $box_bg;
margin-bottom: 20px; margin-bottom: 20px;
color: #666; color: #666;
border: 1px solid #EEE;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget { .ci_widget {
......
...@@ -52,6 +52,10 @@ ...@@ -52,6 +52,10 @@
} }
} }
img {
max-width: 100%;
}
.note_text { .note_text {
width: 100%; width: 100%;
} }
......
...@@ -102,12 +102,3 @@ ...@@ -102,12 +102,3 @@
} }
} }
} }
.profile-groups-avatars {
margin: 0 5px 10px 0;
img {
width: 50px;
height: 50px;
}
}
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
.project-home-panel { .project-home-panel {
margin-bottom: 15px; margin-bottom: 15px;
position: relative;
padding-left: 85px;
&.empty-project { &.empty-project {
border-bottom: 0px; border-bottom: 0px;
...@@ -23,6 +25,22 @@ ...@@ -23,6 +25,22 @@
margin-bottom: 0px; margin-bottom: 0px;
} }
.project-identicon-holder {
position: absolute;
left: 0;
.avatar {
width: 70px;
height: 70px;
@include border-radius(0px);
}
.identicon {
font-size: 45px;
line-height: 1.6;
}
}
.project-home-dropdown { .project-home-dropdown {
margin-left: 10px; margin-left: 10px;
float: right; float: right;
......
...@@ -49,7 +49,7 @@ class ApplicationController < ActionController::Base ...@@ -49,7 +49,7 @@ class ApplicationController < ActionController::Base
end end
def authenticate_user!(*args) def authenticate_user!(*args)
# If user is not signe-in and tries to access root_path - redirect him to landing page # If user is not signed-in and tries to access root_path - redirect him to landing page
if current_application_settings.home_page_url.present? if current_application_settings.home_page_url.present?
if current_user.nil? && controller_name == 'dashboard' && action_name == 'show' if current_user.nil? && controller_name == 'dashboard' && action_name == 'show'
redirect_to current_application_settings.home_page_url and return redirect_to current_application_settings.home_page_url and return
...@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base ...@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base
end end
def add_gon_variables def add_gon_variables
gon.default_issues_tracker = Project.issues_tracker.default_value gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.api_version = API::API.version gon.api_version = API::API.version
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
......
...@@ -63,11 +63,11 @@ class GithubImportsController < ApplicationController ...@@ -63,11 +63,11 @@ class GithubImportsController < ApplicationController
def github_auth def github_auth
if current_user.github_access_token.blank? if current_user.github_access_token.blank?
go_to_gihub_for_permissions go_to_github_for_permissions
end end
end end
def go_to_gihub_for_permissions def go_to_github_for_permissions
redirect_to client.auth_code.authorize_url({ redirect_to client.auth_code.authorize_url({
redirect_uri: callback_github_import_url, redirect_uri: callback_github_import_url,
scope: "repo, user, user:email" scope: "repo, user, user:email"
...@@ -75,6 +75,6 @@ class GithubImportsController < ApplicationController ...@@ -75,6 +75,6 @@ class GithubImportsController < ApplicationController
end end
def github_unauthorized def github_unauthorized
go_to_gihub_for_permissions go_to_github_for_permissions
end end
end end
...@@ -5,12 +5,12 @@ class PasswordsController < Devise::PasswordsController ...@@ -5,12 +5,12 @@ class PasswordsController < Devise::PasswordsController
resource_found = resource_class.find_by_email(email) resource_found = resource_class.find_by_email(email)
if resource_found && resource_found.ldap_user? if resource_found && resource_found.ldap_user?
flash[:alert] = "Cannot reset password for LDAP user." flash[:alert] = "Cannot reset password for LDAP user."
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) and return respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) and return
end end
self.resource = resource_class.send_reset_password_instructions(resource_params) self.resource = resource_class.send_reset_password_instructions(resource_params)
if successfully_sent?(resource) if successfully_sent?(resource)
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
else else
respond_with(resource) respond_with(resource)
end end
......
class Projects::AvatarsController < Projects::ApplicationController
layout 'project'
before_filter :project
def show
@blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
send_data(
@blob.data,
type: @blob.mime_type,
disposition: 'inline',
filename: @blob.name
)
else
not_found!
end
end
def destroy
@project.remove_avatar!
@project.save
@project.reset_events_cache
redirect_to edit_project_path(@project)
end
end
class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath
before_filter :authorize_download_code!
before_filter :require_non_empty_project
end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
class Projects::BlameController < Projects::ApplicationController class Projects::BlameController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
......
...@@ -2,16 +2,70 @@ ...@@ -2,16 +2,70 @@
class Projects::BlobController < Projects::ApplicationController class Projects::BlobController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize # Raised when given an invalid file path
class InvalidPathError < StandardError; end
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project, except: [:new, :create]
before_filter :authorize_push_code!, only: [:destroy] before_filter :authorize_push_code!, only: [:destroy]
before_filter :assign_blob_vars
before_filter :commit, except: [:new, :create]
before_filter :blob, except: [:new, :create]
before_filter :from_merge_request, only: [:edit, :update]
before_filter :after_edit_path, only: [:edit, :update]
before_filter :require_branch_head, only: [:edit, :update]
def new
commit unless @repository.empty?
end
before_filter :blob def create
file_path = File.join(@path, File.basename(params[:file_name]))
result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path))
else
flash[:alert] = result[:message]
render :new
end
end
def show def show
end end
def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
end
def update
result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
if from_merge_request
from_merge_request.reload_code
end
redirect_to after_edit_path
else
flash[:alert] = result[:message]
render :edit
end
end
def preview
@content = params[:content]
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true)
@diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false
end
def destroy def destroy
result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
...@@ -46,10 +100,44 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -46,10 +100,44 @@ class Projects::BlobController < Projects::ApplicationController
if @blob if @blob
@blob @blob
elsif tree.entries.any?
redirect_to project_tree_path(@project, File.join(@ref, @path)) and return
else else
if tree = @repository.tree(@commit.id, @path)
if tree.entries.any?
redirect_to project_tree_path(@project, File.join(@ref, @path)) and return
end
end
return not_found! return not_found!
end end
end end
def commit
@commit = @repository.commit(@ref)
return not_found! unless @commit
end
def assign_blob_vars
@id = params[:id]
@ref, @path = extract_ref(@id)
rescue InvalidPathError
not_found!
end
def after_edit_path
@after_edit_path ||=
if from_merge_request
diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
project_blob_path(@project, @id)
end
end
def from_merge_request
# If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end
end end
...@@ -3,7 +3,7 @@ require "base64" ...@@ -3,7 +3,7 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
......
class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :blob
before_filter :authorize_push_code!
before_filter :from_merge_request
before_filter :after_edit_path
def show
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
end
def update
result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
if from_merge_request
from_merge_request.reload_code
end
redirect_to after_edit_path
else
flash[:alert] = result[:message]
render :show
end
end
def preview
@content = params[:content]
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true)
@diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false
end
private
def blob
@blob ||= @repository.blob_at(@commit.id, @path)
end
def after_edit_path
@after_edit_path ||=
if from_merge_request
diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
project_blob_path(@project, @id)
end
end
def from_merge_request
# If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end
end
...@@ -2,7 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -2,7 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include ApplicationHelper include ApplicationHelper
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
......
class Projects::NewTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :authorize_push_code!
def show
end
def update
file_path = File.join(@path, File.basename(params[:file_name]))
result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path))
else
flash[:alert] = result[:message]
render :show
end
end
end
...@@ -24,7 +24,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
) )
respond_to do |format| respond_to do |format|
format.json { render :json => protected_branch, status: :ok } format.json { render json: protected_branch, status: :ok }
end end
else else
respond_to do |format| respond_to do |format|
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
class Projects::RawController < Projects::ApplicationController class Projects::RawController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
......
class Projects::RefsController < Projects::ApplicationController class Projects::RefsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
# Authorize before_filter :assign_ref_vars
before_filter :authorize_download_code! before_filter :authorize_download_code!
before_filter :require_non_empty_project before_filter :require_non_empty_project
...@@ -41,9 +41,9 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -41,9 +41,9 @@ class Projects::RefsController < Projects::ApplicationController
@path = params[:path] @path = params[:path]
contents = [] contents = []
contents += tree.trees contents.push(*tree.trees)
contents += tree.blobs contents.push(*tree.blobs)
contents += tree.submodules contents.push(*tree.submodules)
@logs = contents[@offset, @limit].to_a.map do |content| @logs = contents[@offset, @limit].to_a.map do |content|
file = @path ? File.join(@path, content.name) : content.name file = @path ? File.join(@path, content.name) : content.name
......
...@@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController
def index def index
@project.build_missing_services @project.build_missing_services
@services = @project.services.reload @services = @project.services.visible.reload
end end
def edit def edit
...@@ -17,7 +17,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -17,7 +17,8 @@ class Projects::ServicesController < Projects::ApplicationController
def update def update
if @service.update_attributes(service_params) if @service.update_attributes(service_params)
redirect_to edit_project_service_path(@project, @service.to_param) redirect_to edit_project_service_path(@project, @service.to_param),
notice: 'Successfully updated.'
else else
render 'edit' render 'edit'
end end
...@@ -25,9 +26,13 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -25,9 +26,13 @@ class Projects::ServicesController < Projects::ApplicationController
def test def test
data = Gitlab::PushDataBuilder.build_sample(project, current_user) data = Gitlab::PushDataBuilder.build_sample(project, current_user)
@service.execute(data) if @service.execute(data)
message = { notice: 'We sent a request to the provided URL' }
else
message = { alert: 'We tried to send a request to the provided URL but error occured' }
end
redirect_to :back redirect_to :back, message
end end
private private
...@@ -41,7 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -41,7 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain, :title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type :build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url
) )
end end
end end
# Controller for viewing a repository's file structure # Controller for viewing a repository's file structure
class Projects::TreeController < Projects::BaseTreeController class Projects::TreeController < Projects::ApplicationController
def show include ExtractsPath
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project, except: [:new, :create]
def show
if tree.entries.empty? if tree.entries.empty?
if @repository.blob_at(@commit.id, @path) if @repository.blob_at(@commit.id, @path)
redirect_to project_blob_path(@project, File.join(@ref, @path)) and return redirect_to project_blob_path(@project, File.join(@ref, @path)) and return
......
...@@ -14,7 +14,7 @@ class ProjectsController < ApplicationController ...@@ -14,7 +14,7 @@ class ProjectsController < ApplicationController
end end
def edit def edit
render 'edit', layout: "project_settings" render 'edit', layout: 'project_settings'
end end
def create def create
...@@ -36,7 +36,7 @@ class ProjectsController < ApplicationController ...@@ -36,7 +36,7 @@ class ProjectsController < ApplicationController
format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' } format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' }
format.js format.js
else else
format.html { render "edit", layout: "project_settings" } format.html { render 'edit', layout: 'project_settings' }
format.js format.js
end end
end end
...@@ -66,17 +66,17 @@ class ProjectsController < ApplicationController ...@@ -66,17 +66,17 @@ class ProjectsController < ApplicationController
format.html do format.html do
if @project.repository_exists? if @project.repository_exists?
if @project.empty_repo? if @project.empty_repo?
render "projects/empty", layout: user_layout render 'projects/empty', layout: user_layout
else else
@last_push = current_user.recent_push(@project.id) if current_user @last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout render :show, layout: user_layout
end end
else else
render "projects/no_repo", layout: user_layout render 'projects/no_repo', layout: user_layout
end end
end end
format.json { pager_json("events/_events", @events.count) } format.json { pager_json('events/_events', @events.count) }
end end
end end
...@@ -87,9 +87,9 @@ class ProjectsController < ApplicationController ...@@ -87,9 +87,9 @@ class ProjectsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
flash[:alert] = "Project deleted." flash[:alert] = 'Project deleted.'
if request.referer.include?("/admin") if request.referer.include?('/admin')
redirect_to admin_projects_path redirect_to admin_projects_path
else else
redirect_to projects_dashboard_path redirect_to projects_dashboard_path
...@@ -101,16 +101,18 @@ class ProjectsController < ApplicationController ...@@ -101,16 +101,18 @@ class ProjectsController < ApplicationController
def autocomplete_sources def autocomplete_sources
note_type = params['type'] note_type = params['type']
note_id = params['type_id'] note_id = params['type_id']
autocomplete = ::Projects::AutocompleteService.new(@project)
participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id) participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id)
@suggestions = { @suggestions = {
emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } }, emojis: autocomplete_emojis,
issues: @project.issues.select([:iid, :title, :description]), issues: autocomplete.issues,
mergerequests: @project.merge_requests.select([:iid, :title, :description]), mergerequests: autocomplete.merge_requests,
members: participants members: participants
} }
respond_to do |format| respond_to do |format|
format.json { render :json => @suggestions } format.json { render json: @suggestions }
end end
end end
...@@ -139,7 +141,7 @@ class ProjectsController < ApplicationController ...@@ -139,7 +141,7 @@ class ProjectsController < ApplicationController
if link_to_image if link_to_image
format.json { render json: { link: link_to_image } } format.json { render json: { link: link_to_image } }
else else
format.json { render json: "Invalid file.", status: :unprocessable_entity } format.json { render json: 'Invalid file.', status: :unprocessable_entity }
end end
end end
end end
...@@ -170,15 +172,26 @@ class ProjectsController < ApplicationController ...@@ -170,15 +172,26 @@ class ProjectsController < ApplicationController
end end
def user_layout def user_layout
current_user ? "projects" : "public_projects" current_user ? 'projects' : 'public_projects'
end end
def project_params def project_params
params.require(:project).permit( params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :merge_requests_template, :wiki_enabled, :merge_requests_template, :visibility_level, :merge_requests_rebase_enabled,
:merge_requests_rebase_enabled :import_url, :last_activity_at, :namespace_id, :avatar
) )
end end
def autocomplete_emojis
Rails.cache.fetch("autocomplete-emoji-#{Emoji::VERSION}") do
Emoji.names.map do |e|
{
name: e,
path: view_context.image_url("emoji/#{e}.png")
}
end
end
end
end end
class UsersController < ApplicationController class UsersController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show] skip_before_filter :authenticate_user!, only: [:show, :activities]
layout :determine_layout layout :determine_layout
def show def show
...@@ -10,7 +10,8 @@ class UsersController < ApplicationController ...@@ -10,7 +10,8 @@ class UsersController < ApplicationController
end end
# Projects user can view # Projects user can view
authorized_projects_ids = ProjectsFinder.new.execute(current_user).pluck(:id) visible_projects = ProjectsFinder.new.execute(current_user)
authorized_projects_ids = visible_projects.pluck(:id)
@projects = @user.personal_projects. @projects = @user.personal_projects.
where(id: authorized_projects_ids) where(id: authorized_projects_ids)
...@@ -24,6 +25,13 @@ class UsersController < ApplicationController ...@@ -24,6 +25,13 @@ class UsersController < ApplicationController
@title = @user.name @title = @user.name
# Get user repositories and collect timestamps for commits
user_repositories = visible_projects.map(&:repository)
calendar = Gitlab::CommitsCalendar.new(user_repositories, @user)
@timestamps = calendar.timestamps
@starting_year = (Time.now - 1.year).strftime("%Y")
@starting_month = Date.today.strftime("%m").to_i
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
......
...@@ -50,6 +50,38 @@ module ApplicationHelper ...@@ -50,6 +50,38 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name } args.any? { |v| v.to_s.downcase == action_name }
end end
def project_icon(project_id, options = {})
project = Project.find_with_namespace(project_id)
if project.avatar.present?
image_tag project.avatar.url, options
elsif project.avatar_in_git
image_tag project_avatar_path(project), options
else # generated icon
project_identicon(project, options)
end
end
def project_identicon(project, options = {})
allowed_colors = {
red: 'FFEBEE',
purple: 'F3E5F5',
indigo: 'E8EAF6',
blue: 'E3F2FD',
teal: 'E0F2F1',
orange: 'FBE9E7',
gray: 'EEEEEE'
}
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
content_tag(:div, class: options[:class],
style: "background-color: ##{ allowed_colors.values[bg_key] }; color: #555") do
project.name[0, 1].upcase
end
end
def group_icon(group_path) def group_icon(group_path)
group = Group.find_by(path: group_path) group = Group.find_by(path: group_path)
if group && group.avatar.present? if group && group.avatar.present?
...@@ -82,24 +114,24 @@ module ApplicationHelper ...@@ -82,24 +114,24 @@ module ApplicationHelper
if project.repo_exists? if project.repo_exists?
time_ago_with_tooltip(project.repository.commit.committed_date) time_ago_with_tooltip(project.repository.commit.committed_date)
else else
"Never" 'Never'
end end
rescue rescue
"Never" 'Never'
end end
def grouped_options_refs def grouped_options_refs
repository = @project.repository repository = @project.repository
options = [ options = [
["Branches", repository.branch_names], ['Branches', repository.branch_names],
["Tags", VersionSorter.rsort(repository.tag_names)] ['Tags', VersionSorter.rsort(repository.tag_names)]
] ]
# If reference is commit id - we should add it to branch/tag selectbox # If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) && if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/) @ref =~ /^[0-9a-zA-Z]{6,52}$/)
options << ["Commit", [@ref]] options << ['Commit', [@ref]]
end end
grouped_options_for_select(options, @ref || @project.default_branch) grouped_options_for_select(options, @ref || @project.default_branch)
...@@ -161,7 +193,7 @@ module ApplicationHelper ...@@ -161,7 +193,7 @@ module ApplicationHelper
path = controller.controller_path.split('/') path = controller.controller_path.split('/')
namespace = path.first if path.second namespace = path.first if path.second
[namespace, controller.controller_name, controller.action_name].compact.join(":") [namespace, controller.controller_name, controller.action_name].compact.join(':')
end end
# shortcut for gitlab config # shortcut for gitlab config
...@@ -176,13 +208,13 @@ module ApplicationHelper ...@@ -176,13 +208,13 @@ module ApplicationHelper
def search_placeholder def search_placeholder
if @project && @project.persisted? if @project && @project.persisted?
"Search in this project" 'Search in this project'
elsif @snippet || @snippets || @show_snippets elsif @snippet || @snippets || @show_snippets
'Search snippets' 'Search snippets'
elsif @group && @group.persisted? elsif @group && @group.persisted?
"Search in this group" 'Search in this group'
else else
"Search" 'Search'
end end
end end
...@@ -193,7 +225,7 @@ module ApplicationHelper ...@@ -193,7 +225,7 @@ module ApplicationHelper
def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago') def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
capture_haml do capture_haml do
haml_tag :time, date.to_s, haml_tag :time, date.to_s,
class: html_class, datetime: date.getutc.iso8601, title: date.stamp("Aug 21, 2011 9:23pm"), class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement } data: { toggle: 'tooltip', placement: placement }
haml_tag :script, "$('." + html_class + "').timeago().tooltip()" haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
...@@ -216,8 +248,8 @@ module ApplicationHelper ...@@ -216,8 +248,8 @@ module ApplicationHelper
end end
def spinner(text = nil, visible = false) def spinner(text = nil, visible = false)
css_class = "loading" css_class = 'loading'
css_class << " hide" unless visible css_class << ' hide' unless visible
content_tag :div, class: css_class do content_tag :div, class: css_class do
content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
...@@ -234,17 +266,17 @@ module ApplicationHelper ...@@ -234,17 +266,17 @@ module ApplicationHelper
absolute_uri = nil absolute_uri = nil
end end
# Add "nofollow" only to external links # Add 'nofollow' only to external links
if host && host != Gitlab.config.gitlab.host && absolute_uri if host && host != Gitlab.config.gitlab.host && absolute_uri
if html_options if html_options
if html_options[:rel] if html_options[:rel]
html_options[:rel] << " nofollow" html_options[:rel] << ' nofollow'
else else
html_options.merge!(rel: "nofollow") html_options.merge!(rel: 'nofollow')
end end
else else
html_options = Hash.new html_options = Hash.new
html_options[:rel] = "nofollow" html_options[:rel] = 'nofollow'
end end
end end
......
...@@ -19,4 +19,42 @@ module BlobHelper ...@@ -19,4 +19,42 @@ module BlobHelper
def no_highlight_files def no_highlight_files
%w(credits changelog copying copyright license authors) %w(credits changelog copying copyright license authors)
end end
def edit_blob_link(project, ref, path, options = {})
blob =
begin
project.repository.blob_at(ref, path)
rescue
nil
end
if blob && blob.text?
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
if allowed_tree_edit?(project, ref)
link_to text, project_edit_blob_path(project, tree_join(ref, path),
link_opts), class: cls
else
content_tag :span, text, class: cls + ' disabled'
end + after.html_safe
else
''
end
end
def leave_edit_message
"Leave edit mode?\nAll unsaved changes will be lost."
end
def editing_preview_title(filename)
if Gitlab::MarkdownHelper.previewable?(filename)
'Preview'
else
'Preview changes'
end
end
end end
...@@ -44,7 +44,7 @@ module CommitsHelper ...@@ -44,7 +44,7 @@ module CommitsHelper
parts = @path.split('/') parts = @path.split('/')
parts.each_with_index do |part, i| parts.each_with_index do |part, i|
crumbs += content_tag(:li) do crumbs << content_tag(:li) do
# The text is just the individual part, but the link needs all the parts before it # The text is just the individual part, but the link needs all the parts before it
link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/'))) link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
end end
...@@ -62,13 +62,27 @@ module CommitsHelper ...@@ -62,13 +62,27 @@ module CommitsHelper
# Returns the sorted alphabetically links to branches, separated by a comma # Returns the sorted alphabetically links to branches, separated by a comma
def commit_branches_links(project, branches) def commit_branches_links(project, branches)
branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe branches.sort.map do |branch|
link_to(project_tree_path(project, branch)) do
content_tag :span, class: 'label label-gray' do
content_tag(:i, nil, class: 'fa fa-code-fork') + ' ' +
branch
end
end
end.join(" ").html_safe
end end
# Returns the sorted links to tags, separated by a comma # Returns the sorted links to tags, separated by a comma
def commit_tags_links(project, tags) def commit_tags_links(project, tags)
sorted = VersionSorter.rsort(tags) sorted = VersionSorter.rsort(tags)
sorted.map { |tag| link_to(tag, project_commits_path(project, project.repository.find_tag(tag).name)) }.join(", ").html_safe sorted.map do |tag|
link_to(project_commits_path(project, project.repository.find_tag(tag).name)) do
content_tag :span, class: 'label label-gray' do
content_tag(:i, nil, class: 'fa fa-tag') + ' ' +
tag
end
end
end.join(" ").html_safe
end end
def link_to_browse_code(project, commit) def link_to_browse_code(project, commit)
......
module GraphHelper module GraphHelper
def get_refs(repo, commit) def get_refs(repo, commit)
refs = "" refs = ""
refs += commit.ref_names(repo).join(" ") refs << commit.ref_names(repo).join(' ')
# append note count # append note count
refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
refs refs
end end
......
...@@ -16,45 +16,25 @@ module IssuesHelper ...@@ -16,45 +16,25 @@ module IssuesHelper
def url_for_project_issues(project = @project) def url_for_project_issues(project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled? project.issues_tracker.project_url
project_issues_path(project)
else
url = Gitlab.config.issues_tracker[project.issues_tracker]['project_url']
url.gsub(':project_id', project.id.to_s).
gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
end
end end
def url_for_new_issue(project = @project) def url_for_new_issue(project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled? project.issues_tracker.new_issue_url
url = new_project_issue_path project_id: project
else
issues_tracker = Gitlab.config.issues_tracker[project.issues_tracker]
url = issues_tracker['new_issue_url']
url.gsub(':project_id', project.id.to_s).
gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
end
end end
def url_for_issue(issue_iid, project = @project) def url_for_issue(issue_iid, project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled? project.issues_tracker.issue_url(issue_iid)
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_iid.to_s).
gsub(':project_id', project.id.to_s).
gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
end
end end
def title_for_issue(issue_iid, project = @project) def title_for_issue(issue_iid, project = @project)
return '' if project.nil? return '' if project.nil?
if project.used_default_issues_tracker? if project.default_issues_tracker?
issue = project.issues.where(iid: issue_iid).first issue = project.issues.where(iid: issue_iid).first
return issue.title if issue return issue.title if issue
end end
...@@ -77,11 +57,6 @@ module IssuesHelper ...@@ -77,11 +57,6 @@ module IssuesHelper
ts.html_safe ts.html_safe
end end
# Checks if issues_tracker setting exists in gitlab.yml
def external_issues_tracker_enabled?
Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any?
end
def bulk_update_milestone_options def bulk_update_milestone_options
options_for_select(['None (backlog)']) + options_for_select(['None (backlog)']) +
options_from_collection_for_select(project_active_milestones, 'id', options_from_collection_for_select(project_active_milestones, 'id',
......
...@@ -72,21 +72,9 @@ module ProjectsHelper ...@@ -72,21 +72,9 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC") @project.milestones.active.order("due_date, title ASC")
end end
def project_issues_trackers(current_tracker = nil)
values = Project.issues_tracker.values.map do |tracker_key|
if tracker_key.to_sym == :gitlab
['GitLab', tracker_key]
else
[Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key]
end
end
options_for_select(values, current_tracker)
end
def link_to_toggle_star(title, starred, signed_in) def link_to_toggle_star(title, starred, signed_in)
cls = 'star-btn' cls = 'star-btn'
cls += ' disabled' unless signed_in cls << ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred toggle_text = if starred
...@@ -187,7 +175,13 @@ module ProjectsHelper ...@@ -187,7 +175,13 @@ module ProjectsHelper
"Issues - " + title "Issues - " + title
end end
elsif current_controller?(:blob) elsif current_controller?(:blob)
"#{@project.path}\/#{@blob.path} at #{@ref} - " + title if current_action?(:new) || current_action?(:create)
"New file at #{@ref}"
elsif current_action?(:show)
"#{@blob.path} at #{@ref}"
elsif @blob
"Edit file #{@blob.path} at #{@ref}"
end
elsif current_controller?(:commits) elsif current_controller?(:commits)
"Commits at #{@ref} - " + title "Commits at #{@ref} - " + title
elsif current_controller?(:merge_requests) elsif current_controller?(:merge_requests)
...@@ -257,7 +251,7 @@ module ProjectsHelper ...@@ -257,7 +251,7 @@ module ProjectsHelper
end end
def github_import_enabled? def github_import_enabled?
Gitlab.config.omniauth.enabled && enabled_oauth_providers.include?(:github) enabled_oauth_providers.include?(:github)
end end
def membership_locked? def membership_locked?
......
...@@ -6,7 +6,7 @@ module TagsHelper ...@@ -6,7 +6,7 @@ module TagsHelper
def tag_list(project) def tag_list(project)
html = '' html = ''
project.tag_list.each do |tag| project.tag_list.each do |tag|
html += link_to tag, tag_path(tag) html << link_to(tag, tag_path(tag))
end end
html.html_safe html.html_safe
......
...@@ -10,13 +10,16 @@ module TreeHelper ...@@ -10,13 +10,16 @@ module TreeHelper
tree = "" tree = ""
# Render folders if we have any # Render folders if we have any
tree += render partial: 'projects/tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present? tree << render(partial: 'projects/tree/tree_item', collection: folders,
locals: { type: 'folder' }) if folders.present?
# Render files if we have any # Render files if we have any
tree += render partial: 'projects/tree/blob_item', collection: files, locals: {type: 'file'} if files.present? tree << render(partial: 'projects/tree/blob_item', collection: files,
locals: { type: 'file' }) if files.present?
# Render submodules if we have any # Render submodules if we have any
tree += render partial: 'projects/tree/submodule_item', collection: submodules if submodules.present? tree << render(partial: 'projects/tree/submodule_item',
collection: submodules) if submodules.present?
tree.html_safe tree.html_safe
end end
...@@ -61,32 +64,6 @@ module TreeHelper ...@@ -61,32 +64,6 @@ module TreeHelper
::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref) ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
end end
def edit_blob_link(project, ref, path, options = {})
blob =
begin
project.repository.blob_at(ref, path)
rescue
nil
end
if blob && blob.text?
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
if allowed_tree_edit?(project, ref)
link_to text, project_edit_tree_path(project, tree_join(ref, path),
link_opts), class: cls
else
content_tag :span, text, class: cls + ' disabled'
end + after.html_safe
else
''
end
end
def tree_breadcrumbs(tree, max_links = 2) def tree_breadcrumbs(tree, max_links = 2)
if @path.present? if @path.present?
part_path = "" part_path = ""
...@@ -109,7 +86,7 @@ module TreeHelper ...@@ -109,7 +86,7 @@ module TreeHelper
tree_join(@ref, file) tree_join(@ref, file)
end end
# returns the relative path of the first subdir that doesn't have only one directory descendand # returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(tree) def flatten_tree(tree)
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path) subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir? if subtree.count == 1 && subtree.first.dir?
...@@ -118,16 +95,4 @@ module TreeHelper ...@@ -118,16 +95,4 @@ module TreeHelper
return tree.name return tree.name
end end
end end
def leave_edit_message
"Leave edit mode?\nAll unsaved changes will be lost."
end
def editing_preview_title(filename)
if Gitlab::MarkdownHelper.previewable?(filename)
'Preview'
else
'Diff'
end
end
end end
...@@ -56,7 +56,7 @@ module Emails ...@@ -56,7 +56,7 @@ module Emails
end end
end end
# Over rides default behavour to show source/target # Over rides default behaviour to show source/target
# Formats arguments into a String suitable for use as an email subject # Formats arguments into a String suitable for use as an email subject
# #
# extra - Extra Strings to be inserted into the subject # extra - Extra Strings to be inserted into the subject
......
...@@ -27,8 +27,8 @@ class Notify < ActionMailer::Base ...@@ -27,8 +27,8 @@ class Notify < ActionMailer::Base
delay_for(2.seconds) delay_for(2.seconds)
end end
def test_email(recepient_email, subject, body) def test_email(recipient_email, subject, body)
mail(to: recepient_email, mail(to: recipient_email,
subject: subject, subject: subject,
body: body.html_safe, body: body.html_safe,
content_type: 'text/html' content_type: 'text/html'
......
...@@ -73,28 +73,28 @@ class Ability ...@@ -73,28 +73,28 @@ class Ability
# Rules based on role in project # Rules based on role in project
if team.master?(user) if team.master?(user)
rules += project_master_rules rules.push(*project_master_rules)
elsif team.developer?(user) elsif team.developer?(user)
rules += project_dev_rules rules.push(*project_dev_rules)
elsif team.reporter?(user) elsif team.reporter?(user)
rules += project_report_rules rules.push(*project_report_rules)
elsif team.guest?(user) elsif team.guest?(user)
rules += project_guest_rules rules.push(*project_guest_rules)
end end
if project.public? || project.internal? if project.public? || project.internal?
rules += public_project_rules rules.push(*public_project_rules)
end end
if project.owner == user || user.admin? if project.owner == user || user.admin?
rules += project_admin_rules rules.push(*project_admin_rules)
end end
if project.group && project.group.has_owner?(user) if project.group && project.group.has_owner?(user)
rules += project_admin_rules rules.push(*project_admin_rules)
end end
if project.archived? if project.archived?
...@@ -193,17 +193,17 @@ class Ability ...@@ -193,17 +193,17 @@ class Ability
# Only group masters and group owners can create new projects in group # Only group masters and group owners can create new projects in group
if group.has_master?(user) || group.has_owner?(user) || user.admin? if group.has_master?(user) || group.has_owner?(user) || user.admin?
rules += [ rules.push(*[
:create_projects, :create_projects,
] ])
end end
# Only group owner and administrators can manage group # Only group owner and administrators can manage group
if group.has_owner?(user) || user.admin? if group.has_owner?(user) || user.admin?
rules += [ rules.push(*[
:manage_group, :manage_group,
:manage_namespace :manage_namespace
] ])
end end
rules.flatten rules.flatten
...@@ -214,10 +214,10 @@ class Ability ...@@ -214,10 +214,10 @@ class Ability
# Only namespace owner and administrators can manage it # Only namespace owner and administrators can manage it
if namespace.owner == user || user.admin? if namespace.owner == user || user.admin?
rules += [ rules.push(*[
:create_projects, :create_projects,
:manage_namespace :manage_namespace
] ])
end end
rules.flatten rules.flatten
......
# == Schema Information
#
# Table name: application_settings
#
# id :integer not null, primary key
# default_projects_limit :integer
# signup_enabled :boolean
# signin_enabled :boolean
# gravatar_enabled :boolean
# sign_in_text :text
# created_at :datetime
# updated_at :datetime
# home_page_url :string(255)
#
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
validates :home_page_url, allow_blank: true, validates :home_page_url, allow_blank: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }, format: { with: URI::regexp(%w(http https)), message: "should be a valid url" },
......
...@@ -88,7 +88,7 @@ module Issuable ...@@ -88,7 +88,7 @@ module Issuable
# Return the number of -1 comments (downvotes) # Return the number of -1 comments (downvotes)
def downvotes def downvotes
notes.select(&:downvote?).size filter_superceded_votes(notes.select(&:downvote?), notes).size
end end
def downvotes_in_percent def downvotes_in_percent
...@@ -101,7 +101,7 @@ module Issuable ...@@ -101,7 +101,7 @@ module Issuable
# Return the number of +1 comments (upvotes) # Return the number of +1 comments (upvotes)
def upvotes def upvotes
notes.select(&:upvote?).size filter_superceded_votes(notes.select(&:upvote?), notes).size
end end
def upvotes_in_percent def upvotes_in_percent
...@@ -124,10 +124,12 @@ module Issuable ...@@ -124,10 +124,12 @@ module Issuable
users << assignee if is_assigned? users << assignee if is_assigned?
mentions = [] mentions = []
mentions << self.mentioned_users mentions << self.mentioned_users
notes.each do |note| notes.each do |note|
users << note.author users << note.author
mentions << note.mentioned_users mentions << note.mentioned_users
end end
users.concat(mentions.reduce([], :|)).uniq users.concat(mentions.reduce([], :|)).uniq
end end
...@@ -149,9 +151,23 @@ module Issuable ...@@ -149,9 +151,23 @@ module Issuable
def add_labels_by_names(label_names) def add_labels_by_names(label_names)
label_names.each do |label_name| label_names.each do |label_name|
label = project.labels.create_with( label = project.labels.create_with(color: Label::DEFAULT_COLOR).
color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip) find_or_create_by(title: label_name.strip)
self.labels << label self.labels << label
end end
end end
private
def filter_superceded_votes(votes, notes)
filteredvotes = [] + votes
votes.each do |vote|
if vote.superceded?(notes)
filteredvotes.delete(vote)
end
end
filteredvotes
end
end end
...@@ -50,7 +50,7 @@ module Mentionable ...@@ -50,7 +50,7 @@ module Mentionable
matches.each do |match| matches.each do |match|
identifier = match.delete "@" identifier = match.delete "@"
if identifier == "all" if identifier == "all"
users += project.team.members.flatten users.push(*project.team.members.flatten)
else else
id = User.find_by(username: identifier).try(:id) id = User.find_by(username: identifier).try(:id)
users << User.find(id) unless id.blank? users << User.find(id) unless id.blank?
......
...@@ -28,6 +28,9 @@ class Group < Namespace ...@@ -28,6 +28,9 @@ class Group < Namespace
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AttachmentUploader
after_create :post_create_hook
after_destroy :post_destroy_hook
def human_name def human_name
name name
end end
...@@ -94,6 +97,18 @@ class Group < Namespace ...@@ -94,6 +97,18 @@ class Group < Namespace
ldap_cn.present? ldap_cn.present?
end end
def post_create_hook
system_hook_service.execute_hooks_for(self, :create)
end
def post_destroy_hook
system_hook_service.execute_hooks_for(self, :destroy)
end
def system_hook_service
SystemHooksService.new
end
class << self class << self
def search(query) def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%") where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
......
# == Schema Information
#
# Table name: identities
#
# id :integer not null, primary key
# extern_uid :string(255)
# provider :string(255)
# user_id :integer
#
class Identity < ActiveRecord::Base class Identity < ActiveRecord::Base
belongs_to :user belongs_to :user
......
...@@ -19,7 +19,7 @@ class Key < ActiveRecord::Base ...@@ -19,7 +19,7 @@ class Key < ActiveRecord::Base
belongs_to :user belongs_to :user
before_validation :strip_white_space, :generate_fingerpint before_validation :strip_white_space, :generate_fingerprint
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
...@@ -78,7 +78,7 @@ class Key < ActiveRecord::Base ...@@ -78,7 +78,7 @@ class Key < ActiveRecord::Base
private private
def generate_fingerpint def generate_fingerprint
self.fingerprint = nil self.fingerprint = nil
return unless key.present? return unless key.present?
......
...@@ -28,8 +28,9 @@ class GroupMember < Member ...@@ -28,8 +28,9 @@ class GroupMember < Member
scope :with_user, ->(user) { where(user_id: user.id) } scope :with_user, ->(user) { where(user_id: user.id) }
scope :with_ldap_dn, -> { joins(user: :identities).where("identities.provider LIKE ?", 'ldap%') } scope :with_ldap_dn, -> { joins(user: :identities).where("identities.provider LIKE ?", 'ldap%') }
after_create :notify_create after_create :post_create_hook
after_update :notify_update after_update :notify_update
after_destroy :post_destroy_hook
def self.access_level_roles def self.access_level_roles
Gitlab::Access.options_with_owner Gitlab::Access.options_with_owner
...@@ -43,8 +44,9 @@ class GroupMember < Member ...@@ -43,8 +44,9 @@ class GroupMember < Member
access_level access_level
end end
def notify_create def post_create_hook
notification_service.new_group_member(self) notification_service.new_group_member(self)
system_hook_service.execute_hooks_for(self, :create)
end end
def notify_update def notify_update
...@@ -53,6 +55,14 @@ class GroupMember < Member ...@@ -53,6 +55,14 @@ class GroupMember < Member
end end
end end
def post_destroy_hook
system_hook_service.execute_hooks_for(self, :destroy)
end
def system_hook_service
SystemHooksService.new
end
def notification_service def notification_service
NotificationService.new NotificationService.new
end end
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
# iid :integer # iid :integer
# description :text # description :text
# position :integer default(0) # position :integer default(0)
# locked_at :datetime
# #
require Rails.root.join("app/models/commit") require Rails.root.join("app/models/commit")
...@@ -253,7 +254,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -253,7 +254,8 @@ class MergeRequest < ActiveRecord::Base
def closes_issues def closes_issues
if target_branch == project.default_branch if target_branch == project.default_branch
issues = commits.flat_map { |c| c.closes_issues(project) } issues = commits.flat_map { |c| c.closes_issues(project) }
issues += Gitlab::ClosingIssueExtractor.closed_by_message_in_project(description, project) issues.push(*Gitlab::ClosingIssueExtractor.
closed_by_message_in_project(description, project))
issues.uniq.sort_by(&:id) issues.uniq.sort_by(&:id)
else else
[] []
...@@ -333,7 +335,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -333,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
end end
# Return array of possible target branches # Return array of possible target branches
# dependes on target project of MR # depends on target project of MR
def target_branches def target_branches
if target_project.nil? if target_project.nil?
[] []
...@@ -343,7 +345,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -343,7 +345,7 @@ class MergeRequest < ActiveRecord::Base
end end
# Return array of possible source branches # Return array of possible source branches
# dependes on source project of MR # depends on source project of MR
def source_branches def source_branches
if source_project.nil? if source_project.nil?
[] []
......
...@@ -84,7 +84,7 @@ module Network ...@@ -84,7 +84,7 @@ module Network
skip += self.class.max_count skip += self.class.max_count
end end
else else
# Cant't find the target commit in the repo. # Can't find the target commit in the repo.
offset = 0 offset = 0
end end
end end
...@@ -226,7 +226,7 @@ module Network ...@@ -226,7 +226,7 @@ module Network
reserved = [] reserved = []
for day in time_range for day in time_range
reserved += @reserved[day] reserved.push(*@reserved[day])
end end
reserved.uniq! reserved.uniq!
......
...@@ -459,6 +459,26 @@ class Note < ActiveRecord::Base ...@@ -459,6 +459,26 @@ class Note < ActiveRecord::Base
) )
end end
def superceded?(notes)
return false unless vote?
notes.each do |note|
next if note == self
if note.vote? &&
self[:author_id] == note[:author_id] &&
self[:created_at] <= note[:created_at]
return true
end
end
false
end
def vote?
upvote? || downvote?
end
def votable? def votable?
for_issue? || (for_merge_request? && !for_diff_line?) for_issue? || (for_merge_request? && !for_diff_line?)
end end
...@@ -480,7 +500,7 @@ class Note < ActiveRecord::Base ...@@ -480,7 +500,7 @@ class Note < ActiveRecord::Base
end end
# FIXME: Hack for polymorphic associations with STI # FIXME: Hack for polymorphic associations with STI
# For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
def noteable_type=(sType) def noteable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s) super(sType.to_s.classify.constantize.base_class.to_s)
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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