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
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
......
......@@ -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"
......
......@@ -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
......
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 @@
#= 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
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()
form.keyup ->
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()
closest_submit.disable()
updateButtons()
form.keyup(updateButtons)
window.sanitize = (str) ->
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
new Profile()
when 'projects'
new Project()
new ProjectAvatar()
switch path[1]
when 'edit'
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
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
......
......@@ -8,6 +8,7 @@
*= require select2
*= require_self
*= require dropzone/basic
*= require cal-heatmap
*/
@import "main/*";
......
......@@ -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 {
......
.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 {
......
......@@ -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;
}
\ No newline at end of file
/*
* 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;
......
......@@ -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));
......
......@@ -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 {
......
......@@ -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%;
}
......@@ -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;
}
}
}
......@@ -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;
}
}
......
......@@ -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;
......
......@@ -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
......
......@@ -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
......@@ -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
......
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 @@
class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_filter :assign_ref_vars
before_filter :authorize_download_code!
before_filter :require_non_empty_project
......
......@@ -2,16 +2,70 @@
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
# Authorize
# Raised when given an invalid file path
class InvalidPathError < StandardError; end
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 :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
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
......
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
......
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|
......
......@@ -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
......
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
# 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
......
......@@ -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
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 }
......
......@@ -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
......
......@@ -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
......@@ -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)
......
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
......
......@@ -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',
......
......@@ -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?
......
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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'
......
......@@ -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
......
# == 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" },
......
......@@ -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
......@@ -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?
......
......@@ -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}%")
......
# == 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
......
......@@ -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?
......
......@@ -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
......
......@@ -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?
[]
......
......@@ -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!
......
......@@ -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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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