Merge branch 'merge_ce' into 'master'

Merge CE into EE

See merge request !302
This commit is contained in:
Dmitriy Zaporozhets
2015-01-30 01:36:39 +00:00
241 changed files with 2965 additions and 1388 deletions
+22 -12
View File
@@ -3,10 +3,10 @@ Note: The upcoming release contains empty lines to reduce the number of merge co
v 7.8.0
- Replace highlight.js with rouge-fork rugments (Stefan Tatschner)
- 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
-
-
- Better UI for project services page
- Cleaner UI for web editor
- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
-
-
@@ -15,28 +15,31 @@ v 7.8.0
-
-
- 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
-
-
-
-
- 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
-
-
-
- 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
- Import from GitHub.com feature
+6 -3
View File
@@ -119,8 +119,8 @@ gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
gem 'sidekiq', '2.17.8'
gem 'sidetiq', '0.6.1'
gem 'sidetiq', '0.6.3'
gem 'sidekiq', '~> 3.3'
# HTTP requests
gem "httparty"
@@ -156,6 +156,9 @@ gem "slack-notifier", "~> 1.0.0"
# d3
gem "d3_rails", "~> 3.1.4"
#cal-heatmap
gem "cal-heatmap-rails", "~> 0.0.1"
# underscore-rails
gem "underscore-rails", "~> 1.4.4"
@@ -172,7 +175,7 @@ gem 'ace-rails-ap'
gem 'mousetrap-rails'
# 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 "coffee-rails"
+20 -16
View File
@@ -52,6 +52,7 @@ GEM
sass (~> 3.2)
browser (0.7.2)
builder (3.2.2)
cal-heatmap-rails (0.0.1)
capybara (2.2.1)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@@ -62,8 +63,8 @@ GEM
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
celluloid (0.15.2)
timers (~> 1.1.0)
celluloid (0.16.0)
timers (~> 4.0.0)
charlock_holmes (0.6.9.4)
cliver (0.3.2)
code_analyzer (0.4.3)
@@ -244,6 +245,7 @@ GEM
hike (1.2.3)
hipchat (1.4.0)
httparty
hitimes (1.2.2)
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
@@ -259,7 +261,7 @@ GEM
httpauth (0.2.1)
httpclient (2.5.3.3)
i18n (0.6.11)
ice_cube (0.12.1)
ice_cube (0.11.1)
ice_nine (0.10.0)
jasmine (2.0.2)
jasmine-core (~> 2.0.0)
@@ -490,22 +492,22 @@ GEM
activesupport (>= 3.1, < 4.2)
select2-rails (3.5.2)
thor (~> 0.14)
semantic-ui-sass (0.16.1.0)
semantic-ui-sass (1.8.0.0)
sass (~> 3.2)
settingslogic (2.0.9)
sexp_processor (4.4.0)
shoulda-matchers (2.1.0)
activesupport (>= 3.0.0)
sidekiq (2.17.8)
celluloid (= 0.15.2)
connection_pool (~> 2.0)
sidekiq (3.3.0)
celluloid (>= 0.16.0)
connection_pool (>= 2.0.0)
json
redis (~> 3.1)
redis-namespace (~> 1.3)
sidetiq (0.6.1)
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sidetiq (0.6.3)
celluloid (>= 0.14.1)
ice_cube (~> 0.12.0)
sidekiq (>= 2.16.0)
ice_cube (= 0.11.1)
sidekiq (>= 3.0.0)
simple_oauth (0.1.9)
simplecov (0.9.0)
docile (~> 1.1.0)
@@ -560,7 +562,8 @@ GEM
thor (0.19.1)
thread_safe (0.3.4)
tilt (1.4.1)
timers (1.1.0)
timers (4.0.1)
hitimes
timfel-krb5-auth (0.8)
tinder (1.9.3)
eventmachine (~> 1.0)
@@ -630,6 +633,7 @@ DEPENDENCIES
binding_of_caller
bootstrap-sass (~> 3.0)
browser
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.2.1)
carrierwave
coffee-rails
@@ -719,11 +723,11 @@ DEPENDENCIES
sdoc
seed-fu
select2-rails
semantic-ui-sass (~> 0.16.1.0)
semantic-ui-sass (~> 1.8.0)
settingslogic
shoulda-matchers (~> 2.1.0)
sidekiq (= 2.17.8)
sidetiq (= 0.6.1)
sidekiq (~> 3.3)
sidetiq (= 0.6.3)
simplecov
sinatra
six
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 B

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1019 B

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 B

After

Width:  |  Height:  |  Size: 49 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

+10 -15
View File
@@ -39,6 +39,7 @@
#= require shortcuts_dashboard_navigation
#= require shortcuts_issueable
#= require shortcuts_network
#= require cal-heatmap
#= require_tree .
window.slugify = (text) ->
@@ -74,24 +75,18 @@ window.disableButtonIfEmptyField = (field_selector, button_selector) ->
# Disable button if any input field with given selector is empty
window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
closest_submit = form.find(button_selector)
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
if empty
closest_submit.disable()
else
closest_submit.enable()
form.keyup ->
empty = false
updateButtons = ->
filled = true
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
filled = rstrip($(this).val()) != "" || !$(this).attr('required')
if empty
closest_submit.disable()
else
if filled
closest_submit.enable()
else
closest_submit.disable()
updateButtons()
form.keyup(updateButtons)
window.sanitize = (str) ->
return str.replace(/<(?:.|\n)*?>/gm, '')
@@ -0,0 +1,44 @@
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
@@ -0,0 +1,21 @@
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
+35
View File
@@ -0,0 +1,35 @@
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
new Profile()
when 'projects'
new Project()
new ProjectAvatar()
switch path[1]
when 'edit'
shortcut_handler = new ShortcutsNavigation()
@@ -0,0 +1,9 @@
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
initEvents: ->
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 ->
checked = $(this).prop("checked")
$("#project_merge_requests_template").prop "disabled", not checked
@@ -6,7 +6,7 @@ class @ProjectShow
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")
$.cookie "default_view", $(e.target).attr("href"), { expires: 30 }
defaultView = $.cookie("default_view")
if defaultView
+1
View File
@@ -8,6 +8,7 @@
*= require select2
*= require_self
*= require dropzone/basic
*= require cal-heatmap
*/
@import "main/*";
+20 -5
View File
@@ -2,15 +2,17 @@
float: left;
margin-right: 12px;
width: 40px;
padding: 1px;
@include border-radius(4px);
height: 40px;
padding: 0;
@include border-radius($avatar_radius);
&.avatar-inline {
float: none;
margin-left: 3px;
margin-left: 4px;
margin-bottom: 2px;
&.s16 { margin-right: 2px; }
&.s24 { margin-right: 2px; }
&.s16 { margin-right: 4px; }
&.s24 { margin-right: 4px; }
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
@@ -21,3 +23,16 @@
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.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 @@
margin-right: 0px;
}
}
&.btn-lg {
font-size: 15px;
line-height: 1.4;
}
}
.btn-block {
@@ -0,0 +1,95 @@
.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 @@
border: none;
border-radius: 0;
font-family: $monospace_font;
font-size: 12px !important;
line-height: 16px !important;
font-size: $code_font_size !important;
line-height: $code_line_height !important;
margin: 0;
overflow: auto;
overflow-y: hidden;
@@ -38,8 +38,8 @@
a {
font-family: $monospace_font;
display: block;
font-size: 12px !important;
line-height: 16px !important;
font-size: $code_font_size !important;
line-height: $code_line_height !important;
white-space: nowrap;
i {
+5 -1
View File
@@ -42,7 +42,7 @@
background: #fff;
color: #737881;
float: left;
@include border-radius(40px);
@include border-radius($avatar_radius);
@include box-shadow(0 0 0 3px #EEE);
overflow: hidden;
@@ -58,6 +58,10 @@
padding: 10px 15px;
margin-left: 60px;
img {
max-width: 100%;
}
&:after {
content: '';
display: block;
@@ -128,3 +128,7 @@ a:focus {
textarea.js-gfm-input {
font-family: $monospace_font;
}
.strikethrough {
text-decoration: line-through;
}
-3
View File
@@ -1,9 +1,6 @@
/*
* 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;
+1 -1
View File
@@ -139,7 +139,7 @@
}
@mixin panel-colored {
border: none;
border: 1px solid #EEE;
background: $box_bg;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
+5 -1
View File
@@ -2,7 +2,7 @@
* General Colors
*/
$style_color: #474D57;
$hover: #FFECDB;
$hover: #FFF3EB;
$box_bg: #F9F9F9;
/*
@@ -57,3 +57,7 @@ $list-font-size: 15px;
* Sidebar navigation width
*/
$sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
@@ -101,7 +101,6 @@
.commit-title {
margin: 0;
font-size: 20px;
}
.commit-description {
+20 -1
View File
@@ -75,6 +75,9 @@
}
}
}
.project-avatar {
float: left;
}
.project-description {
overflow: hidden;
@@ -92,8 +95,24 @@
}
}
.dash-project-avatar {
float: left;
}
.dash-project-access-icon {
float: left;
margin-right: 3px;
margin-right: 5px;
width: 16px;
}
.dash-new-project {
background: $bg_success;
border: 1px solid $border_success;
a {
color: #FFF;
}
}
.dash-list .str-truncated {
max-width: 72%;
}
+3 -3
View File
@@ -37,7 +37,7 @@
overflow-y: hidden;
background: #FFF;
color: #333;
font-size: 12px;
font-size: $code_font_size;
.old {
span.idiff {
background-color: #F99;
@@ -64,8 +64,8 @@
margin: 0px;
padding: 0px;
td {
line-height: 18px;
font-size: 12px;
line-height: $code_line_height;
font-size: $code_font_size;
}
}
@@ -31,4 +31,26 @@
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;
}
}
}
+4 -3
View File
@@ -138,9 +138,10 @@ header {
top: -1px;
padding-right: 0px !important;
img {
width: 26px;
height: 26px;
@include border-radius(4px);
width: 50px;
height: 50px;
margin: -15px;
margin-left: 5px;
}
}
+2 -1
View File
@@ -163,8 +163,9 @@ form.edit-issue {
}
}
.issue-title {
h3.issue-title {
margin-top: 0;
font-size: 2em;
}
.context .select2-container {
@@ -122,6 +122,7 @@
background: $box_bg;
margin-bottom: 20px;
color: #666;
border: 1px solid #EEE;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget {
@@ -52,6 +52,10 @@
}
}
img {
max-width: 100%;
}
.note_text {
width: 100%;
}
@@ -102,12 +102,3 @@
}
}
}
.profile-groups-avatars {
margin: 0 5px 10px 0;
img {
width: 50px;
height: 50px;
}
}
@@ -16,6 +16,8 @@
.project-home-panel {
margin-bottom: 15px;
position: relative;
padding-left: 85px;
&.empty-project {
border-bottom: 0px;
@@ -23,6 +25,22 @@
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 {
margin-left: 10px;
float: right;
+2 -2
View File
@@ -49,7 +49,7 @@ class ApplicationController < ActionController::Base
end
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_user.nil? && controller_name == 'dashboard' && action_name == 'show'
redirect_to current_application_settings.home_page_url and return
@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base
end
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.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
+3 -3
View File
@@ -63,11 +63,11 @@ class GithubImportsController < ApplicationController
def github_auth
if current_user.github_access_token.blank?
go_to_gihub_for_permissions
go_to_github_for_permissions
end
end
def go_to_gihub_for_permissions
def go_to_github_for_permissions
redirect_to client.auth_code.authorize_url({
redirect_uri: callback_github_import_url,
scope: "repo, user, user:email"
@@ -75,6 +75,6 @@ class GithubImportsController < ApplicationController
end
def github_unauthorized
go_to_gihub_for_permissions
go_to_github_for_permissions
end
end
+2 -2
View File
@@ -5,12 +5,12 @@ class PasswordsController < Devise::PasswordsController
resource_found = resource_class.find_by_email(email)
if resource_found && resource_found.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
self.resource = resource_class.send_reset_password_instructions(resource_params)
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
respond_with(resource)
end
@@ -0,0 +1,29 @@
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
@@ -1,7 +0,0 @@
class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath
before_filter :authorize_download_code!
before_filter :require_non_empty_project
end
+1 -1
View File
@@ -2,7 +2,7 @@
class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
+95 -7
View File
@@ -2,16 +2,70 @@
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :authorize_push_code!, only: [:destroy]
# Raised when given an invalid file path
class InvalidPathError < StandardError; end
before_filter :blob
before_filter :authorize_download_code!
before_filter :require_non_empty_project, except: [:new, :create]
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
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
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
result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
@@ -46,10 +100,44 @@ class Projects::BlobController < Projects::ApplicationController
if @blob
@blob
elsif tree.entries.any?
redirect_to project_tree_path(@project, File.join(@ref, @path)) and return
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!
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
@@ -3,7 +3,7 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
@@ -1,60 +0,0 @@
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
include ExtractsPath
include ApplicationHelper
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
@@ -1,20 +0,0 @@
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
)
respond_to do |format|
format.json { render :json => protected_branch, status: :ok }
format.json { render json: protected_branch, status: :ok }
end
else
respond_to do |format|
+1 -1
View File
@@ -2,7 +2,7 @@
class Projects::RawController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
+4 -4
View File
@@ -1,7 +1,7 @@
class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
@@ -41,9 +41,9 @@ class Projects::RefsController < Projects::ApplicationController
@path = params[:path]
contents = []
contents += tree.trees
contents += tree.blobs
contents += tree.submodules
contents.push(*tree.trees)
contents.push(*tree.blobs)
contents.push(*tree.submodules)
@logs = contents[@offset, @limit].to_a.map do |content|
file = @path ? File.join(@path, content.name) : content.name
@@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController
def index
@project.build_missing_services
@services = @project.services.reload
@services = @project.services.visible.reload
end
def edit
@@ -17,7 +17,8 @@ class Projects::ServicesController < Projects::ApplicationController
def update
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
render 'edit'
end
@@ -25,9 +26,13 @@ class Projects::ServicesController < Projects::ApplicationController
def test
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
private
@@ -41,7 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
: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
+7 -2
View File
@@ -1,7 +1,12 @@
# Controller for viewing a repository's file structure
class Projects::TreeController < Projects::BaseTreeController
def show
class Projects::TreeController < Projects::ApplicationController
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 @repository.blob_at(@commit.id, @path)
redirect_to project_blob_path(@project, File.join(@ref, @path)) and return
+28 -15
View File
@@ -14,7 +14,7 @@ class ProjectsController < ApplicationController
end
def edit
render 'edit', layout: "project_settings"
render 'edit', layout: 'project_settings'
end
def create
@@ -36,7 +36,7 @@ class ProjectsController < ApplicationController
format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' }
format.js
else
format.html { render "edit", layout: "project_settings" }
format.html { render 'edit', layout: 'project_settings' }
format.js
end
end
@@ -66,17 +66,17 @@ class ProjectsController < ApplicationController
format.html do
if @project.repository_exists?
if @project.empty_repo?
render "projects/empty", layout: user_layout
render 'projects/empty', layout: user_layout
else
@last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout
end
else
render "projects/no_repo", layout: user_layout
render 'projects/no_repo', layout: user_layout
end
end
format.json { pager_json("events/_events", @events.count) }
format.json { pager_json('events/_events', @events.count) }
end
end
@@ -87,9 +87,9 @@ class ProjectsController < ApplicationController
respond_to do |format|
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
else
redirect_to projects_dashboard_path
@@ -101,16 +101,18 @@ class ProjectsController < ApplicationController
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
autocomplete = ::Projects::AutocompleteService.new(@project)
participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id)
@suggestions = {
emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } },
issues: @project.issues.select([:iid, :title, :description]),
mergerequests: @project.merge_requests.select([:iid, :title, :description]),
emojis: autocomplete_emojis,
issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests,
members: participants
}
respond_to do |format|
format.json { render :json => @suggestions }
format.json { render json: @suggestions }
end
end
@@ -139,7 +141,7 @@ class ProjectsController < ApplicationController
if link_to_image
format.json { render json: { link: link_to_image } }
else
format.json { render json: "Invalid file.", status: :unprocessable_entity }
format.json { render json: 'Invalid file.', status: :unprocessable_entity }
end
end
end
@@ -170,15 +172,26 @@ class ProjectsController < ApplicationController
end
def user_layout
current_user ? "projects" : "public_projects"
current_user ? 'projects' : 'public_projects'
end
def project_params
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list,
: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,
:merge_requests_rebase_enabled
:wiki_enabled, :merge_requests_template, :visibility_level, :merge_requests_rebase_enabled,
:import_url, :last_activity_at, :namespace_id, :avatar
)
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
+10 -2
View File
@@ -1,5 +1,5 @@
class UsersController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show]
skip_before_filter :authenticate_user!, only: [:show, :activities]
layout :determine_layout
def show
@@ -10,7 +10,8 @@ class UsersController < ApplicationController
end
# 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.
where(id: authorized_projects_ids)
@@ -24,6 +25,13 @@ class UsersController < ApplicationController
@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|
format.html
format.atom { render layout: false }
+48 -16
View File
@@ -50,6 +50,38 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name }
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)
group = Group.find_by(path: group_path)
if group && group.avatar.present?
@@ -82,24 +114,24 @@ module ApplicationHelper
if project.repo_exists?
time_ago_with_tooltip(project.repository.commit.committed_date)
else
"Never"
'Never'
end
rescue
"Never"
'Never'
end
def grouped_options_refs
repository = @project.repository
options = [
["Branches", repository.branch_names],
["Tags", VersionSorter.rsort(repository.tag_names)]
['Branches', repository.branch_names],
['Tags', VersionSorter.rsort(repository.tag_names)]
]
# If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/)
options << ["Commit", [@ref]]
options << ['Commit', [@ref]]
end
grouped_options_for_select(options, @ref || @project.default_branch)
@@ -161,7 +193,7 @@ module ApplicationHelper
path = controller.controller_path.split('/')
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
# shortcut for gitlab config
@@ -176,13 +208,13 @@ module ApplicationHelper
def search_placeholder
if @project && @project.persisted?
"Search in this project"
'Search in this project'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
"Search in this group"
'Search in this group'
else
"Search"
'Search'
end
end
@@ -193,7 +225,7 @@ module ApplicationHelper
def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
capture_haml do
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 }
haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
@@ -216,8 +248,8 @@ module ApplicationHelper
end
def spinner(text = nil, visible = false)
css_class = "loading"
css_class << " hide" unless visible
css_class = 'loading'
css_class << ' hide' unless visible
content_tag :div, class: css_class do
content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
@@ -234,17 +266,17 @@ module ApplicationHelper
absolute_uri = nil
end
# Add "nofollow" only to external links
# Add 'nofollow' only to external links
if host && host != Gitlab.config.gitlab.host && absolute_uri
if html_options
if html_options[:rel]
html_options[:rel] << " nofollow"
html_options[:rel] << ' nofollow'
else
html_options.merge!(rel: "nofollow")
html_options.merge!(rel: 'nofollow')
end
else
html_options = Hash.new
html_options[:rel] = "nofollow"
html_options[:rel] = 'nofollow'
end
end
+38
View File
@@ -19,4 +19,42 @@ module BlobHelper
def no_highlight_files
%w(credits changelog copying copyright license authors)
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
+17 -3
View File
@@ -44,7 +44,7 @@ module CommitsHelper
parts = @path.split('/')
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
link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
end
@@ -62,13 +62,27 @@ module CommitsHelper
# Returns the sorted alphabetically links to branches, separated by a comma
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
# Returns the sorted links to tags, separated by a comma
def commit_tags_links(project, 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
def link_to_browse_code(project, commit)
+2 -2
View File
@@ -1,10 +1,10 @@
module GraphHelper
def get_refs(repo, commit)
refs = ""
refs += commit.ref_names(repo).join(" ")
refs << commit.ref_names(repo).join(' ')
# 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
end
+4 -29
View File
@@ -16,45 +16,25 @@ module IssuesHelper
def url_for_project_issues(project = @project)
return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
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
project.issues_tracker.project_url
end
def url_for_new_issue(project = @project)
return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
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
project.issues_tracker.new_issue_url
end
def url_for_issue(issue_iid, project = @project)
return '' if project.nil?
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
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
project.issues_tracker.issue_url(issue_iid)
end
def title_for_issue(issue_iid, project = @project)
return '' if project.nil?
if project.used_default_issues_tracker?
if project.default_issues_tracker?
issue = project.issues.where(iid: issue_iid).first
return issue.title if issue
end
@@ -77,11 +57,6 @@ module IssuesHelper
ts.html_safe
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
options_for_select(['None (backlog)']) +
options_from_collection_for_select(project_active_milestones, 'id',
+9 -15
View File
@@ -72,21 +72,9 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC")
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)
cls = 'star-btn'
cls += ' disabled' unless signed_in
cls << ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred
@@ -187,7 +175,13 @@ module ProjectsHelper
"Issues - " + title
end
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)
"Commits at #{@ref} - " + title
elsif current_controller?(:merge_requests)
@@ -257,7 +251,7 @@ module ProjectsHelper
end
def github_import_enabled?
Gitlab.config.omniauth.enabled && enabled_oauth_providers.include?(:github)
enabled_oauth_providers.include?(:github)
end
def membership_locked?
+1 -1
View File
@@ -6,7 +6,7 @@ module TagsHelper
def tag_list(project)
html = ''
project.tag_list.each do |tag|
html += link_to tag, tag_path(tag)
html << link_to(tag, tag_path(tag))
end
html.html_safe
+7 -42
View File
@@ -10,13 +10,16 @@ module TreeHelper
tree = ""
# 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
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
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
end
@@ -61,32 +64,6 @@ module TreeHelper
::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
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)
if @path.present?
part_path = ""
@@ -109,7 +86,7 @@ module TreeHelper
tree_join(@ref, file)
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)
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir?
@@ -118,16 +95,4 @@ module TreeHelper
return tree.name
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
+1 -1
View File
@@ -56,7 +56,7 @@ module Emails
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
#
# extra - Extra Strings to be inserted into the subject
+2 -2
View File
@@ -27,8 +27,8 @@ class Notify < ActionMailer::Base
delay_for(2.seconds)
end
def test_email(recepient_email, subject, body)
mail(to: recepient_email,
def test_email(recipient_email, subject, body)
mail(to: recipient_email,
subject: subject,
body: body.html_safe,
content_type: 'text/html'
+13 -13
View File
@@ -73,28 +73,28 @@ class Ability
# Rules based on role in project
if team.master?(user)
rules += project_master_rules
rules.push(*project_master_rules)
elsif team.developer?(user)
rules += project_dev_rules
rules.push(*project_dev_rules)
elsif team.reporter?(user)
rules += project_report_rules
rules.push(*project_report_rules)
elsif team.guest?(user)
rules += project_guest_rules
rules.push(*project_guest_rules)
end
if project.public? || project.internal?
rules += public_project_rules
rules.push(*public_project_rules)
end
if project.owner == user || user.admin?
rules += project_admin_rules
rules.push(*project_admin_rules)
end
if project.group && project.group.has_owner?(user)
rules += project_admin_rules
rules.push(*project_admin_rules)
end
if project.archived?
@@ -193,17 +193,17 @@ class Ability
# Only group masters and group owners can create new projects in group
if group.has_master?(user) || group.has_owner?(user) || user.admin?
rules += [
rules.push(*[
:create_projects,
]
])
end
# Only group owner and administrators can manage group
if group.has_owner?(user) || user.admin?
rules += [
rules.push(*[
:manage_group,
:manage_namespace
]
])
end
rules.flatten
@@ -214,10 +214,10 @@ class Ability
# Only namespace owner and administrators can manage it
if namespace.owner == user || user.admin?
rules += [
rules.push(*[
:create_projects,
:manage_namespace
]
])
end
rules.flatten
+15
View File
@@ -1,3 +1,18 @@
# == 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
validates :home_page_url, allow_blank: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" },
+20 -4
View File
@@ -88,7 +88,7 @@ module Issuable
# Return the number of -1 comments (downvotes)
def downvotes
notes.select(&:downvote?).size
filter_superceded_votes(notes.select(&:downvote?), notes).size
end
def downvotes_in_percent
@@ -101,7 +101,7 @@ module Issuable
# Return the number of +1 comments (upvotes)
def upvotes
notes.select(&:upvote?).size
filter_superceded_votes(notes.select(&:upvote?), notes).size
end
def upvotes_in_percent
@@ -124,10 +124,12 @@ module Issuable
users << assignee if is_assigned?
mentions = []
mentions << self.mentioned_users
notes.each do |note|
users << note.author
mentions << note.mentioned_users
end
users.concat(mentions.reduce([], :|)).uniq
end
@@ -149,9 +151,23 @@ module Issuable
def add_labels_by_names(label_names)
label_names.each do |label_name|
label = project.labels.create_with(
color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip)
label = project.labels.create_with(color: Label::DEFAULT_COLOR).
find_or_create_by(title: label_name.strip)
self.labels << label
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
+1 -1
View File
@@ -50,7 +50,7 @@ module Mentionable
matches.each do |match|
identifier = match.delete "@"
if identifier == "all"
users += project.team.members.flatten
users.push(*project.team.members.flatten)
else
id = User.find_by(username: identifier).try(:id)
users << User.find(id) unless id.blank?
+15
View File
@@ -28,6 +28,9 @@ class Group < Namespace
mount_uploader :avatar, AttachmentUploader
after_create :post_create_hook
after_destroy :post_destroy_hook
def human_name
name
end
@@ -94,6 +97,18 @@ class Group < Namespace
ldap_cn.present?
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
def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
+11 -1
View File
@@ -1,5 +1,15 @@
# == 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
belongs_to :user
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
end
end
+2 -2
View File
@@ -19,7 +19,7 @@ class Key < ActiveRecord::Base
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 :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
@@ -78,7 +78,7 @@ class Key < ActiveRecord::Base
private
def generate_fingerpint
def generate_fingerprint
self.fingerprint = nil
return unless key.present?
+12 -2
View File
@@ -28,8 +28,9 @@ class GroupMember < Member
scope :with_user, ->(user) { where(user_id: user.id) }
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_destroy :post_destroy_hook
def self.access_level_roles
Gitlab::Access.options_with_owner
@@ -43,8 +44,9 @@ class GroupMember < Member
access_level
end
def notify_create
def post_create_hook
notification_service.new_group_member(self)
system_hook_service.execute_hooks_for(self, :create)
end
def notify_update
@@ -53,6 +55,14 @@ class GroupMember < Member
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
NotificationService.new
end
+5 -3
View File
@@ -18,6 +18,7 @@
# iid :integer
# description :text
# position :integer default(0)
# locked_at :datetime
#
require Rails.root.join("app/models/commit")
@@ -253,7 +254,8 @@ class MergeRequest < ActiveRecord::Base
def closes_issues
if target_branch == project.default_branch
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)
else
[]
@@ -333,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
end
# Return array of possible target branches
# dependes on target project of MR
# depends on target project of MR
def target_branches
if target_project.nil?
[]
@@ -343,7 +345,7 @@ class MergeRequest < ActiveRecord::Base
end
# Return array of possible source branches
# dependes on source project of MR
# depends on source project of MR
def source_branches
if source_project.nil?
[]
+2 -2
View File
@@ -84,7 +84,7 @@ module Network
skip += self.class.max_count
end
else
# Cant't find the target commit in the repo.
# Can't find the target commit in the repo.
offset = 0
end
end
@@ -226,7 +226,7 @@ module Network
reserved = []
for day in time_range
reserved += @reserved[day]
reserved.push(*@reserved[day])
end
reserved.uniq!
+21 -1
View File
@@ -459,6 +459,26 @@ class Note < ActiveRecord::Base
)
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?
for_issue? || (for_merge_request? && !for_diff_line?)
end
@@ -480,7 +500,7 @@ class Note < ActiveRecord::Base
end
# 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)
super(sType.to_s.classify.constantize.base_class.to_s)
end
+98 -44
View File
@@ -14,7 +14,7 @@
# merge_requests_enabled :boolean default(TRUE), not null
# wiki_enabled :boolean default(TRUE), not null
# namespace_id :integer
# issues_tracker :string(255) default("gitlab"), not null
# issues_tracker :string(255) default('gitlab'), not null
# issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime
@@ -24,8 +24,14 @@
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
# import_type :string(255)
# import_source :string(255)
# avatar :string(255)
#
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Project < ActiveRecord::Base
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
@@ -47,8 +53,8 @@ class Project < ActiveRecord::Base
attr_accessor :new_default_branch
# Relations
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id"
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace
has_one :git_hook, dependent: :destroy
@@ -71,21 +77,27 @@ class Project < ActiveRecord::Base
has_one :bamboo_service, dependent: :destroy
has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
has_one :jira_service, dependent: :destroy
has_one :redmine_service, dependent: :destroy
has_one :custom_issue_tracker_service, dependent: :destroy
has_one :gitlab_issue_tracker_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
has_many :milestones, dependent: :destroy
has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet'
has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy
has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :users, through: :project_members
@@ -120,28 +132,31 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
format: { with: URI::regexp(%w(git http https)), message: 'should be a valid url' },
if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :avatar_type,
if: ->(project) { project.avatar && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader
# Scopes
scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
scope :without_team, ->(team) { team.projects.present? ? where("projects.id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped }
scope :not_in_group, ->(group) { where("projects.id NOT IN (:ids)", ids: group.project_ids ) }
scope :in_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) }
scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
scope :in_team, ->(team) { where('projects.id IN (:ids)', ids: team.projects.map(&:id)) }
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 :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 :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
state_machine :import_status, initial: :none do
event :import_start do
transition [:none, :finished] => :started
@@ -177,7 +192,7 @@ class Project < ActiveRecord::Base
def publicish(user)
visibility_levels = [Project::PUBLIC]
visibility_levels += [Project::INTERNAL] if user
visibility_levels << Project::INTERNAL if user
where(visibility_level: visibility_levels)
end
@@ -186,26 +201,26 @@ class Project < ActiveRecord::Base
end
def active
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
end
def search(query)
joins(:namespace).where("projects.archived = ?", false).
where("LOWER(projects.name) LIKE :query OR
joins(:namespace).where('projects.archived = ?', false).
where('LOWER(projects.name) LIKE :query OR
LOWER(projects.path) LIKE :query OR
LOWER(namespaces.name) LIKE :query OR
LOWER(projects.description) LIKE :query",
LOWER(projects.description) LIKE :query',
query: "%#{query.try(:downcase)}%")
end
def search_by_title(query)
where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%")
where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end
def find_with_namespace(id)
return nil unless id.include?("/")
return nil unless id.include?('/')
id = id.split("/")
id = id.split('/')
namespace = Namespace.find_by(path: id.first)
return nil unless namespace
@@ -223,7 +238,7 @@ class Project < ActiveRecord::Base
when 'recently_updated' then reorder('projects.updated_at DESC')
when 'last_updated' then reorder('projects.updated_at ASC')
when 'largest_repository' then reorder('projects.repository_size DESC')
else reorder("namespaces.path, projects.name ASC")
else reorder('namespaces.path, projects.name ASC')
end
end
end
@@ -273,19 +288,19 @@ class Project < ActiveRecord::Base
end
def to_param
namespace.path + "/" + path
namespace.path + '/' + path
end
def web_url
[gitlab_config.url, path_with_namespace].join("/")
[gitlab_config.url, path_with_namespace].join('/')
end
def web_url_without_protocol
web_url.split("://")[1]
web_url.split('://')[1]
end
def build_commit_note(commit)
notes.new(commit_id: commit.id, noteable_type: "Commit")
notes.new(commit_id: commit.id, noteable_type: 'Commit')
end
def last_activity
@@ -301,19 +316,43 @@ class Project < ActiveRecord::Base
end
def issue_exists?(issue_id)
if used_default_issues_tracker?
if default_issues_tracker?
self.issues.where(iid: issue_id).first.present?
else
true
end
end
def used_default_issues_tracker?
self.issues_tracker == Project.issues_tracker.default_value
def default_issue_tracker
gitlab_issue_tracker_service ||= create_gitlab_issue_tracker_service
end
def issues_tracker
if external_issue_tracker
external_issue_tracker
else
default_issue_tracker
end
end
def default_issues_tracker?
if external_issue_tracker
false
else
true
end
end
def external_issues_trackers
services.select(&:issue_tracker?).reject(&:default?)
end
def external_issue_tracker
@external_issues_tracker ||= external_issues_trackers.select(&:activated?).first
end
def can_have_issues_tracker_id?
self.issues_enabled && !self.used_default_issues_tracker?
self.issues_enabled && !self.default_issues_tracker?
end
def build_missing_services
@@ -328,7 +367,8 @@ class Project < ActiveRecord::Base
def available_services_names
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira jenkins)
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jenkins jira redmine custom_issue_tracker
)
end
def gitlab_ci?
@@ -343,12 +383,25 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first
end
def jira_tracker?
self.issues_tracker == "jira"
def jira_tracker?
issues_tracker.to_param == 'jira'
end
def redmine_tracker?
self.issues_tracker == "redmine"
issues_tracker.to_param == 'redmine'
end
def avatar_type
unless self.avatar.image?
self.errors.add :avatar, 'only images allowed'
end
end
def avatar_in_git
@avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png')
@avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg')
@avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif')
@avatar_file
end
# For compatibility with old code
@@ -378,7 +431,7 @@ class Project < ActiveRecord::Base
end
def team_member_by_name_or_email(name = nil, email = nil)
user = users.where("name like ? or email like ?", name, email).first
user = users.where('name like ? or email like ?', name, email).first
project_members.where(user: user) if user
end
@@ -390,7 +443,7 @@ class Project < ActiveRecord::Base
def name_with_namespace
@name_with_namespace ||= begin
if namespace
namespace.human_name + " / " + name
namespace.human_name + ' / ' + name
else
name
end
@@ -425,7 +478,7 @@ class Project < ActiveRecord::Base
def valid_repo?
repository.exists?
rescue
errors.add(:path, "Invalid repository path")
errors.add(:path, 'Invalid repository path')
false
end
@@ -484,7 +537,7 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo
[gitlab_config.url, "/", path_with_namespace, ".git"].join('')
[gitlab_config.url, '/', path_with_namespace, '.git'].join('')
end
# Check if current branch name is marked as protected in the system
@@ -547,6 +600,7 @@ class Project < ActiveRecord::Base
# Since we do cache @event we need to reset cache in special cases:
# * when project was moved
# * when project was renamed
# * when the project avatar changes
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
@@ -619,7 +673,7 @@ class Project < ActiveRecord::Base
if gitlab_shell.add_repository(path_with_namespace)
true
else
errors.add(:base, "Failed to create repository")
errors.add(:base, 'Failed to create repository')
false
end
end
@@ -632,7 +686,7 @@ class Project < ActiveRecord::Base
ProjectWiki.new(self, self.owner).wiki
true
rescue ProjectWiki::CouldNotCreateWikiError => ex
errors.add(:base, "Failed create wiki")
errors.add(:base, 'Failed create wiki')
false
end
end

Some files were not shown because too many files have changed in this diff Show More