Commit d8b71ed8 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into flevour/gitlab-ce-fix-merge-request-textareas

parents 322913f3 41b08e4a
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.0.0 (unreleased) v 8.1.0 (unreleased)
- Include full path of source and target branch names in New Merge Request page (Stan Hu)
- Add user preference to view activities as default dashboard (Stan Hu)
- Add option to admin area to sign in as a specific user (Pavel Forkert)
- Show CI status on all pages where commits list is rendered
- Automatically enable CI when push .gitlab-ci.yml file to repository
- Move CI charts to project graphs area
- Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg)
- Show CI status on commit page
- Show CI status on Your projects page and Starred projects page
- Remove "Continuous Integration" page from dashboard
- Add notes and SSL verification entries to hook APIs (Ben Boeckel)
- Fix grammar in admin area "labels" .nothing-here-block when no labels exist.
- Move CI runners page to project settings area
- Move CI variables page to project settings area
- Move CI triggers page to project settings area
- Move CI project settings page to CE project settings area
- Fix bug when removed file was not appearing in merge request diff
- Note the original location of a moved project when notifying users of the move
- Improve error message when merging fails
v 8.0.3
- Fix URL shown in Slack notifications
- Fix bug where projects would appear to be stuck in the forked import state (Stan Hu)
- Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu)
v 8.0.2
- Fix default avatar not rendering in network graph (Stan Hu)
- Skip check_initd_configured_correctly on omnibus installs
- Prevent double-prefixing of help page paths
- Clarify confirmation text on user deletion
- Make commit graphs responsive to window width changes (Stan Hu)
- Fix top margin for sign-in button on public pages
- Fix LDAP attribute mapping
- Remove git refs used internally by GitLab from network graph (Stan Hu)
- Use standard Markdown font in Markdown preview instead of fixed-width font (Stan Hu)
- Fix Reply by email for non-UTF-8 messages.
- Add option to use StartTLS with Reply by email IMAP server.
- Allow AWS S3 Server-Side Encryption with Amazon S3-Managed Keys for backups (Paul Beattie)
v 8.0.1
- Remove git refs used internally by GitLab from network graph (Stan Hu)
- Improve CI migration procedure and documentation
v 8.0.0
- Fix Markdown links not showing up in dashboard activity feed (Stan Hu)
- Remove milestones from merge requests when milestones are deleted (Stan Hu)
- Fix HTML link that was improperly escaped in new user e-mail (Stan Hu) - Fix HTML link that was improperly escaped in new user e-mail (Stan Hu)
- Fix broken sort in merge request API (Stan Hu) - Fix broken sort in merge request API (Stan Hu)
- Bump rouge to 1.10.1 to remove warning noise and fix other syntax highlighting bugs (Stan Hu) - Bump rouge to 1.10.1 to remove warning noise and fix other syntax highlighting bugs (Stan Hu)
...@@ -42,6 +89,9 @@ v 8.0.0 (unreleased) ...@@ -42,6 +89,9 @@ v 8.0.0 (unreleased)
- Retrieving oauth token with LDAP credentials - Retrieving oauth token with LDAP credentials
- Load Application settings from running database unless env var USE_DB=false - Load Application settings from running database unless env var USE_DB=false
- Added Drone CI integration (Kirill Zaitsev) - Added Drone CI integration (Kirill Zaitsev)
- Allow developers to retry builds
- Hide advanced project options for non-admin users
- Fail builds if no .gitlab-ci.yml is found
- Refactored service API and added automatically service docs generator (Kirill Zaitsev) - Refactored service API and added automatically service docs generator (Kirill Zaitsev)
- Added web_url key project hook_attrs (Kirill Zaitsev) - Added web_url key project hook_attrs (Kirill Zaitsev)
- Add ability to get user information by ID of an SSH key via the API - Add ability to get user information by ID of an SSH key via the API
...@@ -49,6 +99,7 @@ v 8.0.0 (unreleased) ...@@ -49,6 +99,7 @@ v 8.0.0 (unreleased)
- Add support for Crowd - Add support for Crowd
- Global Labels that are available to all projects - Global Labels that are available to all projects
- Fix highlighting of deleted lines in diffs. - Fix highlighting of deleted lines in diffs.
- Project notification level can be set on the project page itself
- Added service API endpoint to retrieve service parameters (Petheő Bence) - Added service API endpoint to retrieve service parameters (Petheő Bence)
- Add FogBugz project import (Jared Szechy) - Add FogBugz project import (Jared Szechy)
- Sort users autocomplete lists by user (Allister Antosik) - Sort users autocomplete lists by user (Allister Antosik)
...@@ -56,6 +107,8 @@ v 8.0.0 (unreleased) ...@@ -56,6 +107,8 @@ v 8.0.0 (unreleased)
- Add ability to add custom text to the help page (Jeroen van Baarsen) - Add ability to add custom text to the help page (Jeroen van Baarsen)
- Add pg_schema to backup config - Add pg_schema to backup config
- Fix references to target project issues in Merge Requests markdown preview and textareas (Francesco Levorato) - Fix references to target project issues in Merge Requests markdown preview and textareas (Francesco Levorato)
- Redirect from incorrectly cased group or project path to correct one (Francesco Levorato)
- Removed API calls from CE to CI
v 7.14.3 v 7.14.3
- No changes - No changes
......
This diff is collapsed.
...@@ -22,7 +22,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql ...@@ -22,7 +22,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries # Authentication libraries
gem "devise", '~> 3.2.4' gem "devise", '~> 3.5.2'
gem "devise-async", '~> 0.9.0' gem "devise-async", '~> 0.9.0'
gem 'omniauth', "~> 1.2.2" gem 'omniauth', "~> 1.2.2"
gem 'omniauth-google-oauth2', '~> 0.2.5' gem 'omniauth-google-oauth2', '~> 0.2.5'
...@@ -38,7 +38,7 @@ gem 'omniauth_crowd' ...@@ -38,7 +38,7 @@ gem 'omniauth_crowd'
gem "rack-oauth2", "~> 1.0.5" gem "rack-oauth2", "~> 1.0.5"
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 1.0.1' gem 'devise-two-factor', '~> 2.0.0'
gem 'rqrcode-rails3', '~> 0.1.7' gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 1.3.4' gem 'attr_encrypted', '~> 1.3.4'
...@@ -77,7 +77,7 @@ gem "stamp", '~> 0.5.0' ...@@ -77,7 +77,7 @@ gem "stamp", '~> 0.5.0'
gem 'enumerize', '~> 0.7.0' gem 'enumerize', '~> 0.7.0'
# Pagination # Pagination
gem "kaminari", "~> 0.15.1" gem "kaminari", "~> 0.16.3"
# HAML # HAML
gem "haml-rails", '~> 0.5.3' gem "haml-rails", '~> 0.5.3'
...@@ -121,6 +121,8 @@ end ...@@ -121,6 +121,8 @@ end
# State machine # State machine
gem "state_machine", '~> 1.2.0' gem "state_machine", '~> 1.2.0'
# Run events after state machine commits
gem 'after_commit_queue'
# Issue tags # Issue tags
gem 'acts-as-taggable-on', '~> 3.4' gem 'acts-as-taggable-on', '~> 3.4'
...@@ -283,10 +285,11 @@ group :production do ...@@ -283,10 +285,11 @@ group :production do
end end
gem "newrelic_rpm", '~> 3.9.4.245' gem "newrelic_rpm", '~> 3.9.4.245'
gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0' gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.5.1" gem "mail_room", "~> 0.5.2"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
......
...@@ -42,6 +42,8 @@ GEM ...@@ -42,6 +42,8 @@ GEM
acts-as-taggable-on (3.5.0) acts-as-taggable-on (3.5.0)
activerecord (>= 3.2, < 5) activerecord (>= 3.2, < 5)
addressable (2.3.8) addressable (2.3.8)
after_commit_queue (1.1.0)
rails (>= 3.0)
annotate (2.6.10) annotate (2.6.10)
activerecord (>= 3.2, <= 4.3) activerecord (>= 3.2, <= 4.3)
rake (~> 10.4) rake (~> 10.4)
...@@ -136,21 +138,21 @@ GEM ...@@ -136,21 +138,21 @@ GEM
activerecord (>= 3.2.0, < 5.0) activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
devise (3.2.4) devise (3.5.2)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5) railties (>= 3.2.6, < 5)
responders
thread_safe (~> 0.1) thread_safe (~> 0.1)
warden (~> 1.2.3) warden (~> 1.2.3)
devise-async (0.9.0) devise-async (0.9.0)
devise (~> 3.2) devise (~> 3.2)
devise-two-factor (1.0.2) devise-two-factor (2.0.0)
activemodel
activesupport activesupport
attr_encrypted (~> 1.3.2) attr_encrypted (~> 1.3.2)
devise (>= 3.2.4, < 3.5) devise (~> 3.5.0)
railties railties
rotp (< 2) rotp (~> 2)
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.7) diffy (3.0.7)
docile (1.1.5) docile (1.1.5)
...@@ -367,7 +369,7 @@ GEM ...@@ -367,7 +369,7 @@ GEM
railties (>= 3.2.16) railties (>= 3.2.16)
json (1.8.3) json (1.8.3)
jwt (1.5.1) jwt (1.5.1)
kaminari (0.15.1) kaminari (0.16.3)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
kgio (2.9.3) kgio (2.9.3)
...@@ -384,7 +386,7 @@ GEM ...@@ -384,7 +386,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.3)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
mail_room (0.5.1) mail_room (0.5.2)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -402,6 +404,9 @@ GEM ...@@ -402,6 +404,9 @@ GEM
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (2.9.2) net-ssh (2.9.2)
netrc (0.10.3) netrc (0.10.3)
newrelic-grape (2.0.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245) newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2) nokogiri (1.6.6.2)
mini_portile (~> 0.6.0) mini_portile (~> 0.6.0)
...@@ -558,12 +563,14 @@ GEM ...@@ -558,12 +563,14 @@ GEM
request_store (1.2.0) request_store (1.2.0)
rerun (0.10.0) rerun (0.10.0)
listen (~> 2.7, >= 2.7.3) listen (~> 2.7, >= 2.7.3)
responders (1.1.2)
railties (>= 3.2, < 4.2)
rest-client (1.8.0) rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0) mime-types (>= 1.16, < 3.0)
netrc (~> 0.7) netrc (~> 0.7)
rinku (1.7.3) rinku (1.7.3)
rotp (1.6.1) rotp (2.1.1)
rouge (1.10.1) rouge (1.10.1)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
...@@ -782,6 +789,7 @@ DEPENDENCIES ...@@ -782,6 +789,7 @@ DEPENDENCIES
activerecord-session_store (~> 0.1.0) activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8) addressable (~> 2.3.8)
after_commit_queue
annotate (~> 2.6.0) annotate (~> 2.6.0)
asana (~> 0.0.6) asana (~> 0.0.6)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.2)
...@@ -806,9 +814,9 @@ DEPENDENCIES ...@@ -806,9 +814,9 @@ DEPENDENCIES
d3_rails (~> 3.5.5) d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0) database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 3.2.4) devise (~> 3.5.2)
devise-async (~> 0.9.0) devise-async (~> 0.9.0)
devise-two-factor (~> 1.0.1) devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3) diffy (~> 3.0.3)
doorkeeper (~> 2.1.3) doorkeeper (~> 2.1.3)
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
...@@ -844,13 +852,14 @@ DEPENDENCIES ...@@ -844,13 +852,14 @@ DEPENDENCIES
jquery-scrollto-rails (~> 1.4.3) jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.0.1) jquery-turbolinks (~> 2.0.1)
jquery-ui-rails (~> 4.2.1) jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.15.1) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener (~> 1.1.2)
mail_room (~> 0.5.1) mail_room (~> 0.5.2)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
nested_form (~> 0.3.2) nested_form (~> 0.3.2)
newrelic-grape
newrelic_rpm (~> 3.9.4.245) newrelic_rpm (~> 3.9.4.245)
nprogress-rails (~> 0.1.2.3) nprogress-rails (~> 0.1.2.3)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
......
8.0.0.pre 8.1.0.pre
@CiPager =
init: (@url, @limit = 0, preload, @disable = false) ->
if preload
@offset = 0
@getItems()
else
@offset = @limit
@initLoadMore()
getItems: ->
$(".loading").show()
$.ajax
type: "GET"
url: @url
data: "limit=" + @limit + "&offset=" + @offset
complete: =>
$(".loading").hide()
success: (data) =>
CiPager.append(data.count, data.html)
dataType: "json"
append: (count, html) ->
if count > 1
$(".content-list").append html
if count == @limit
@offset += count
else
@disable = true
initLoadMore: ->
$(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: ->
CiPager.disable
callback: (i) =>
unless $(".loading").is(':visible')
$(".loading").show()
CiPager.getItems()
...@@ -11,12 +11,13 @@ class @IssuableContext ...@@ -11,12 +11,13 @@ class @IssuableContext
$(this).submit() $(this).submit()
$('.issuable-details').waitForImages -> $('.issuable-details').waitForImages ->
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
$('.issuable-affix').affix offset: $('.issuable-affix').affix offset:
top: -> top: ->
@top = ($('.issuable-affix').offset().top - 70) @top = ($('.issuable-affix').offset().top - 70)
bottom: -> bottom: ->
@bottom = $('.footer').outerHeight(true) @bottom = $('.footer').outerHeight(true)
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
...@@ -15,11 +15,12 @@ class @MergeRequestWidget ...@@ -15,11 +15,12 @@ class @MergeRequestWidget
type: 'GET' type: 'GET'
url: $('.merge-request').data('url') url: $('.merge-request').data('url')
success: (data) => success: (data) =>
switch data.state if data.state == "merged"
when 'merged' location.reload()
location.reload() else if data.merge_error
else $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
setTimeout(merge_request_widget.mergeInProgress, 2000) else
setTimeout(merge_request_widget.mergeInProgress, 2000)
dataType: 'json' dataType: 'json'
getMergeStatus: -> getMergeStatus: ->
......
...@@ -24,3 +24,19 @@ class @Project ...@@ -24,3 +24,19 @@ class @Project
$.cookie('hide_no_password_message', 'false', { path: path }) $.cookie('hide_no_password_message', 'false', { path: path })
$(@).parents('.no-password-message').remove() $(@).parents('.no-password-message').remove()
e.preventDefault() e.preventDefault()
$('.update-notification').on 'click', (e) ->
e.preventDefault()
notification_level = $(@).data 'notification-level'
$('#notification_level').val(notification_level)
$('#notification-form').submit()
label = null
switch notification_level
when 0 then label = ' Disabled '
when 1 then label = ' Participating '
when 2 then label = ' Watching '
when 3 then label = ' Global '
when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
\ No newline at end of file
...@@ -65,20 +65,20 @@ $legend-color: $text-color; ...@@ -65,20 +65,20 @@ $legend-color: $text-color;
// //
//## //##
$pagination-color: #fff; $pagination-color: $gl-gray;
$pagination-bg: $brand-success; $pagination-bg: $background-color;
$pagination-border: transparent; $pagination-border: transparent;
$pagination-hover-color: #fff; $pagination-hover-color: #fff;
$pagination-hover-bg: darken($brand-success, 15%); $pagination-hover-bg: $brand-info;
$pagination-hover-border: transparent; $pagination-hover-border: transparent;
$pagination-active-color: #fff; $pagination-active-color: #fff;
$pagination-active-bg: darken($brand-success, 15%); $pagination-active-bg: $brand-info;
$pagination-active-border: transparent; $pagination-active-border: transparent;
$pagination-disabled-color: #b4bcc2; $pagination-disabled-color: #fff;
$pagination-disabled-bg: lighten($brand-success, 15%); $pagination-disabled-bg: lighten($brand-info, 15%);
$pagination-disabled-border: transparent; $pagination-disabled-border: transparent;
......
...@@ -93,46 +93,88 @@ ...@@ -93,46 +93,88 @@
} }
h1 { h1 {
margin-top: 45px; font-size: 1.3em;
font-size: 2.5em; font-weight: 600;
margin: 24px 0 12px 0;
padding: 0 0 10px 0;
border-bottom: 1px solid #e7e9ed;
color: #313236;
} }
h2 { h2 {
margin-top: 40px; font-size: 1.2em;
font-size: 2em; font-weight: 600;
margin: 24px 0 12px 0;
color: #313236;
} }
h3 { h3 {
margin-top: 35px; margin: 24px 0 12px 0;
font-size: 1.5em; font-size: 1.25em;
} }
h4 { h4 {
margin-top: 30px; margin: 24px 0 12px 0;
font-size: 1.2em; font-size: 1.1em;
}
h5 {
margin: 24px 0 12px 0;
font-size: 1em;
}
h6 {
margin: 24px 0 12px 0;
font-size: 0.90em;
} }
blockquote { blockquote {
color: #888; padding: 8px 21px;
margin: 12px 0 12px;
border-left: 3px solid #e7e9ed;
}
blockquote p {
color: #7f8fa4 !important;
font-size: 15px; font-size: 15px;
line-height: 1.5; line-height: 1.5;
} }
p {
color:#5c5d5e;
margin:6px 0 0 0;
}
table { table {
@extend .table; @extend .table;
@extend .table-bordered; @extend .table-bordered;
margin: 12px 0 12px 0;
color: #5c5d5e;
th { th {
background: #EEE; background: #f8fafc;
} }
} }
pre {
margin: 12px 0 12px 0 !important;
background-color: #f8fafc !important;
font-size: 13px !important;
color: #5b6169 !important;
line-height: 1.6em !important;
@include border-radius(2px);
}
p > code { p > code {
font-size: inherit;
font-weight: inherit; font-weight: inherit;
} }
ul {
color: #5c5d5e;
}
li { li {
line-height: 1.5; line-height: 1.6em;
} }
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] { a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
...@@ -152,6 +194,7 @@ ...@@ -152,6 +194,7 @@
} }
} }
@mixin str-truncated($max_width: 82%) { @mixin str-truncated($max_width: 82%) {
display: inline-block; display: inline-block;
overflow: hidden; overflow: hidden;
...@@ -183,7 +226,7 @@ ...@@ -183,7 +226,7 @@
&.active { &.active {
background: #f9f9f9; background: #f9f9f9;
a { a {
font-weight: bold; font-weight: 600;
} }
} }
...@@ -251,3 +294,8 @@ ...@@ -251,3 +294,8 @@
} }
} }
} }
.fa-align {
top: 20px;
position: relative;
}
...@@ -12,8 +12,8 @@ $sidebar_width: 230px; ...@@ -12,8 +12,8 @@ $sidebar_width: 230px;
$avatar_radius: 50%; $avatar_radius: 50%;
$code_font_size: 13px; $code_font_size: 13px;
$code_line_height: 1.5; $code_line_height: 1.5;
$border-color: #E7E9ED; $border-color: #dce0e6;
$background-color: #F8FAFC; $background-color: #F7F8FA;
$header-height: 58px; $header-height: 58px;
$fixed-layout-width: 1200px; $fixed-layout-width: 1200px;
$gl-gray: #7f8fa4; $gl-gray: #7f8fa4;
......
...@@ -13,31 +13,6 @@ ...@@ -13,31 +13,6 @@
.builds, .builds,
.projects-table { .projects-table {
.alert-success {
background-color: #6fc995;
border-color: #5bba83;
}
.alert-danger {
background-color: #eb897f;
border-color: #d4776e;
}
.alert-info {
background-color: #3498db;
border-color: #2e8ece;
}
.alert-warning {
background-color: #EB974E;
border-color: #E87E04;
}
.alert-disabled {
background: $background-color;
border-color: $border-color;
}
.light { .light {
border-color: $border-color; border-color: $border-color;
} }
...@@ -47,8 +22,8 @@ ...@@ -47,8 +22,8 @@
} }
td { td {
color: $gl-gray;
vertical-align: middle !important; vertical-align: middle !important;
border-color: inherit !important;
a { a {
font-weight: normal; font-weight: normal;
...@@ -58,23 +33,16 @@ ...@@ -58,23 +33,16 @@
} }
.commit-info { .commit-info {
font-size: 14px;
.attr-name { .attr-name {
font-weight: 300;
color: #666;
margin-right: 5px; margin-right: 5px;
} }
pre.commit-message { pre.commit-message {
font-size: 14px;
background: none; background: none;
padding: 0; padding: 0;
margin: 0; margin: 0;
border: none; border: none;
margin: 20px 0; margin: 20px 0;
border-bottom: 1px solid #EEE;
padding-bottom: 20px;
border-radius: 0; border-radius: 0;
} }
} }
......
.ci-status {
padding: 2px 7px;
margin-right: 5px;
border: 1px solid #EEE;
white-space: nowrap;
@include border-radius(4px);
&:hover {
text-decoration: none;
}
&.ci-failed {
color: $gl-danger;
border-color: $gl-danger;
}
&.ci-success {
color: $gl-success;
border-color: $gl-success;
}
&.ci-info {
color: $gl-info;
border-color: $gl-info;
}
&.ci-disabled {
color: $gl-gray;
border-color: $gl-gray;
}
&.ci-pending,
&.ci-running {
color: $gl-warning;
border-color: $gl-warning;
}
}
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
&.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; } &.s140 { width: 140px; height: 140px; margin-right: 20px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; } &.s160 { width: 160px; height: 160px; margin-right: 20px; }
} }
...@@ -42,6 +43,7 @@ ...@@ -42,6 +43,7 @@
&.s32 { font-size: 22px; line-height: 32px; } &.s32 { font-size: 22px; line-height: 32px; }
&.s60 { font-size: 32px; line-height: 60px; } &.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; } &.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
&.s140 { font-size: 72px; line-height: 140px; } &.s140 { font-size: 72px; line-height: 140px; }
&.s160 { font-size: 96px; line-height: 160px; } &.s160 { font-size: 96px; line-height: 160px; }
} }
...@@ -20,11 +20,11 @@ ...@@ -20,11 +20,11 @@
.gray-content-block { .gray-content-block {
margin: -$gl-padding; margin: -$gl-padding;
background-color: #f8fafc; background-color: $background-color;
padding: $gl-padding; padding: $gl-padding;
margin-bottom: 0px; margin-bottom: 0px;
border-top: 1px solid #e7e9ed; border-top: 1px solid $border-color;
border-bottom: 1px solid #e7e9ed; border-bottom: 1px solid $border-color;
color: $gl-gray; color: $gl-gray;
&.top-block { &.top-block {
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
&.footer-block { &.footer-block {
margin-top: 0; margin-top: 0;
border-bottom: none;
margin-bottom: -$gl-padding; margin-bottom: -$gl-padding;
} }
......
body {
text-rendering: geometricPrecision;
}
.btn { .btn {
@extend .btn-default; @extend .btn-default;
...@@ -88,3 +91,138 @@ ...@@ -88,3 +91,138 @@
} }
} }
} }
@mixin btn-info {
@include border-radius(2px);
border-width: 1px;
border-style: solid;
text-transform: uppercase;
font-size: 13px;
font-weight: 600;
line-height: 18px;
padding: 11px 16px;
letter-spacing: .4px;
&:hover {
border-width: 1px;
border-style: solid;
}
&:focus {
border-width: 1px;
border-style: solid;
}
&:active {
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
border-width: 1px;
border-style: solid;
}
}
@mixin btn-middle {
@include border-radius(2px);
border-width: 1px;
border-style: solid;
text-transform: uppercase;
font-size: 13px;
font-weight: 600;
line-height: 18px;
padding: 11px 24px;
letter-spacing: .4px;
&:hover {
border-width: 1px;
border-style: solid;
}
&:focus {
border-width: 1px;
border-style: solid;
}
&:active {
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
border-width: 1px;
border-style: solid;
}
}
@mixin btn-green {
background-color: #28b061;
border: 1px solid #26a65c;
color: #fff;
&:hover {
background-color: #26ab5d;
border: 1px solid #229954;
color: #fff;
}
&:focus {
background-color: #26ab5d;
border: 1px solid #229954;
color: #fff;
}
&:active {
@include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12));
background-color: #23a158 !important;
border: 1px solid #229954 !important;
color: #fff !important;
}
}
/*Butons*/
@mixin bnt-project {
background-color: #f0f2f5;
border-color: #dce0e5;
color: #313236;
&:hover {
border-color:#dce0e5;
background-color: #ebeef2;
color: #313236;
}
&:focus {
border-color: #dce0e5;
background-color: #ebeef2;
color: #313236;
}
&:active {
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
color: #313236 !important;
border-color: #c6cacf !important;
background-color: #e4e7ed !important;
}
}
@mixin btn-remove {
background-color: #f72e60;
border-color: #ee295a;
&:hover {
background-color: #e82757;
border-color: #e32555;
}
&:focus {
background-color: #e82757;
border-color: #e32555;
}
&:active {
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
background-color: #d42450 !important;
border-color: #e12554 !important;
}
}
\ No newline at end of file
/*
* Callouts from Bootstrap3 docs
*
* Not quite alerts, but custom and helpful notes for folks reading the docs.
* Requires a base and modifier class.
*/
/* Common styles for all types */
.bs-callout {
margin: 20px 0;
padding: 20px;
border-left: 3px solid #eee;
color: #666;
background: #f9f9f9;
}
.bs-callout h4 {
margin-top: 0;
margin-bottom: 5px;
}
.bs-callout p:last-child {
margin-bottom: 0;
}
/* Variations */
.bs-callout-danger {
background-color: #fdf7f7;
border-color: #eed3d7;
color: #b94a48;
}
.bs-callout-warning {
background-color: #faf8f0;
border-color: #faebcc;
color: #8a6d3b;
}
.bs-callout-info {
background-color: #f4f8fa;
border-color: #bce8f1;
color: #34789a;
}
.bs-callout-success {
background-color: #dff0d8;
border-color: #5cA64d;
color: #3c763d;
}
...@@ -302,7 +302,7 @@ table { ...@@ -302,7 +302,7 @@ table {
} }
.btn-sign-in { .btn-sign-in {
margin-top: 15px; margin-top: 8px;
text-shadow: none; text-shadow: none;
} }
...@@ -313,7 +313,7 @@ table { ...@@ -313,7 +313,7 @@ table {
} }
.wiki .highlight, .note-body .highlight { .wiki .highlight, .note-body .highlight {
margin-bottom: 9px; margin: 12px 0 12px 0;
} }
.wiki .code { .wiki .code {
......
...@@ -26,7 +26,6 @@ header { ...@@ -26,7 +26,6 @@ header {
min-height: $header-height; min-height: $header-height;
background-color: #fff; background-color: #fff;
border: none; border: none;
border-bottom: 1px solid #EEE;
.container-fluid { .container-fluid {
width: 100% !important; width: 100% !important;
......
.gl-pagination {
border-top: 1px solid $border-color;
background-color: $background-color;
margin: -$gl-padding;
margin-top: 0;
.pagination {
padding: 0;
margin: 0;
display: block;
li.first,
li.last,
li.next,
li.prev {
> a {
color: $link-color;
&:hover {
color: #fff;
}
}
}
li > a,
li > span {
border: none;
margin: 0;
@include border-radius(0 !important);
padding: 13px 19px;
border-right: 1px solid $border-color;
}
}
}
...@@ -21,12 +21,11 @@ ...@@ -21,12 +21,11 @@
min-height: 100vh; min-height: 100vh;
width: 100%; width: 100%;
padding: 20px; padding: 20px;
background: #f1f4f8; background: #EAEBEC;
.container-fluid { .container-fluid {
background: #FFF; background: #FFF;
padding: $gl-padding; padding: $gl-padding;
border: 1px solid #e7e9ed;
min-height: 90vh; min-height: 90vh;
&.container-blank { &.container-blank {
......
...@@ -2,11 +2,24 @@ ...@@ -2,11 +2,24 @@
* Headers * Headers
* *
*/ */
body {
text-rendering:optimizeLegibility;
-webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px;
}
.page-title { .page-title {
margin-top: 0px; margin-top: 0px;
line-height: 1.5; line-height: 1.3;
font-weight: normal; font-size: 1.25em;
margin-bottom: 5px; font-weight: 600;
}
.page-title-empty {
margin-top: 0px;
line-height: 1.3;
font-size: 1.25em;
font-weight: 600;
margin: 12px 7px 12px 7px;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
...@@ -55,6 +68,7 @@ a > code { ...@@ -55,6 +68,7 @@ a > code {
@include md-typography; @include md-typography;
word-wrap: break-word; word-wrap: break-word;
padding: 7px;
/* Link to current header. */ /* Link to current header. */
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
...@@ -83,12 +97,19 @@ a > code { ...@@ -83,12 +97,19 @@ a > code {
} }
} }
ul { ul,ol {
padding: 0; padding: 0;
margin: 0 0 9px 25px !important; margin: 6px 0 6px 18px !important;
}
ol {
color: #5c5d5e;
} }
} }
.md-area {
@include md-typography;
}
.md { .md {
@include md-typography; @include md-typography;
} }
...@@ -101,6 +122,9 @@ textarea.js-gfm-input { ...@@ -101,6 +122,9 @@ textarea.js-gfm-input {
font-family: $monospace_font; font-family: $monospace_font;
} }
.md-preview {
}
.strikethrough { .strikethrough {
text-decoration: line-through; text-decoration: line-through;
} }
\ No newline at end of file
/* https://github.com/aahan/pygments-github-style */ /* https://github.com/aahan/pygments-github-style */
pre.code.highlight.white, pre.code.highlight.white,
.code.white { .code.white {
background-color: #f8fafc;
background-color: #fff; font-size: 13px;
color: #333; color: #5b6169;
line-height: 1.6em;
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
......
...@@ -10,3 +10,9 @@ ...@@ -10,3 +10,9 @@
.milestone-row { .milestone-row {
@include str-truncated(90%); @include str-truncated(90%);
} }
.dashboard .side .panel .panel-heading .input-group {
.form-control {
height: 42px;
}
}
\ No newline at end of file
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
.note-edit-form { .note-edit-form {
display: none; display: none;
font-size: 13px; font-size: 15px;
.form-actions { .form-actions {
padding-left: 20px; padding-left: 20px;
......
.alert_holder {
margin: -16px;
.alert-link {
font-weight: normal;
}
}
.no-ssh-key-message {
background-color: #f28d35;
margin-bottom: 16px;
}
.new_project, .new_project,
.edit_project { .edit_project {
fieldset.features { fieldset.features {
...@@ -19,44 +30,47 @@ ...@@ -19,44 +30,47 @@
background: #f7f8fa; background: #f7f8fa;
margin: -$gl-padding; margin: -$gl-padding;
padding: $gl-padding; padding: $gl-padding;
padding-top: 40px; padding: 44px 0 17px 0;
.project-identicon-holder { .project-identicon-holder {
margin-bottom: 15px; margin-bottom: 16px;
.avatar, .identicon { .avatar, .identicon {
margin: 0 auto; margin: 0 auto;
float: none; float: none;
} }
.identicon { .identicon {
@include border-radius(50%); @include border-radius(50%);
} }
} }
.project-home-dropdown { .project-home-dropdown {
margin: 11px 3px 0; margin: 11px 3px 0;
} }
.project-home-desc { .project-home-desc {
h1 { h1 {
color: #313236;
margin: 0; margin: 0;
margin-bottom: 10px; margin-bottom: 6px;
font-size: 23px; font-size: 23px;
font-weight: normal; font-weight: normal;
} }
p { p {
color: #7f8fa4; color: #5c5d5e;
} }
} }
.git-clone-holder { .git-clone-holder {
max-width: 600px; max-width: 498px;
margin: 20px auto;
.form-control { .form-control {
background: #FFF; background: #FFF;
font-size: 14px;
height: 42px;
margin-left: -1px;
} }
} }
...@@ -66,30 +80,37 @@ ...@@ -66,30 +80,37 @@
color: inherit; color: inherit;
} }
} }
.input-group {
display: inline-table;
position: relative;
top: 17px;
margin-bottom: 44px;
}
.project-repo-buttons { .project-repo-buttons {
margin-top: $gl-padding; margin-top: 12px;
margin-bottom: 25px; margin-bottom: 0px;
.btn { .btn {
@extend .btn-info; @include bnt-project;
@include btn-info;
text-transform: uppercase;
font-size: 15px;
line-height: 20px;
padding: 8px 14px;
border-radius: 3px;
margin-left: 10px;
.count { .count {
padding-left: 7px;
display: inline-block; display: inline-block;
margin-left: 7px;
} }
} }
} }
} }
.split-one {
display: inline-table;
margin-right: 12px;
a {
margin: -1px !important;
}
}
.git-clone-holder { .git-clone-holder {
.project-home-dropdown + & { .project-home-dropdown + & {
margin-right: 45px; margin-right: 45px;
...@@ -99,23 +120,132 @@ ...@@ -99,23 +120,132 @@
cursor: auto; cursor: auto;
@extend .monospace; @extend .monospace;
background: #FAFAFA; background: #FAFAFA;
width: 100%; width: 101%;
} }
.input-group-addon { .input-group-addon {
background: #FAFAFA; background: #f7f8fa;
&.git-protocols { &.git-protocols {
padding: 0; padding: 0;
border: none; border: none;
.input-group-btn:last-child > .btn { .input-group-btn:last-child > .btn {
@include border-radius-right(0); @include border-radius-right(0);
border-left: 1px solid #c6cacf;
margin-left: -2px !important;
} }
} }
} }
} }
.projects-search-form {
.input-group .form-control {
height: 42px;
}
}
.input-group-btn {
.btn {
@include bnt-project;
@include btn-middle;
&:hover {
outline: none;
}
&:focus {
outline: none;
}
&:active {
outline: none;
}
}
.active {
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
}
.btn-green {
@include btn-green
}
}
.split-repo-buttons {
display: inline-table;
margin: 0 12px 0 12px;
.btn{
@include bnt-project;
@include btn-info;
}
.dropdown-toggle {
margin: -5px;
}
}
#notification-form {
margin-left: 5px;
}
.dropdown-new {
margin-left: -5px;
}
.open > .dropdown-new.btn {
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
text-transform: uppercase;
color: #313236 !important;
font-size: 13px;
font-weight: 600;
}
.dropdown-menu {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
@include border-radius (0px);
border: none;
padding: 16px 0;
font-size: 14px;
font-weight: 100;
li a {
color: #5f697a;
line-height: 30px;
&:hover {
background-color: #3084bb !important;
}
}
.fa-fw {
margin-right: 8px;
}
}
.fa-bell {
margin-right: 6px;
}
.fa-angle-down {
margin-left: 6px;
}
.project-home-panel .project-home-dropdown {
margin: 13px 0px 0;
}
.project-visibility-level-holder { .project-visibility-level-holder {
.radio { .radio {
margin-bottom: 10px; margin-bottom: 10px;
...@@ -232,15 +362,28 @@ table.table.protected-branches-list tr.no-border { ...@@ -232,15 +362,28 @@ table.table.protected-branches-list tr.no-border {
.project-stats { .project-stats {
text-align: center; text-align: center;
margin-top: 0; margin-top: 15px;
margin-bottom: 0; margin-bottom: 0;
padding-top: 5px; padding-top: 10px;
padding-bottom: 0; padding-bottom: 4px;
ul.nav-pills { ul.nav-pills {
display:inline-block; display:inline-block;
} }
.nav-pills li {
display:inline;
}
.nav > li > a {
@include btn-info;
@include bnt-project;
background-color: transparent;
border: 1px solid #f7f8fa;
margin-left: 12px;
}
li { li {
display:inline; display:inline;
} }
...@@ -251,11 +394,11 @@ table.table.protected-branches-list tr.no-border { ...@@ -251,11 +394,11 @@ table.table.protected-branches-list tr.no-border {
} }
li.missing a { li.missing a {
color: #bbb; color: #5a6069;
border: 1px dashed #ccc; border: 1px dashed #dce0e5;
&:hover { &:hover {
background-color: #FAFAFA; background-color: #f0f2f5;
} }
} }
} }
...@@ -273,9 +416,37 @@ pre.light-well { ...@@ -273,9 +416,37 @@ pre.light-well {
border-bottom: 1px solid #e7e9ed; border-bottom: 1px solid #e7e9ed;
} }
.git-empty {
margin: 0 7px 0 7px;
h5 {
color: #5c5d5e;
}
.light-well {
@include border-radius (2px);
color: #5b6169;
font-size: 13px;
line-height: 1.6em;
}
}
.prepend-top-20 {
margin-top: 20px;
.btn-remove {
@include btn-middle;
@include btn-remove;
float: left !important;
}
}
/* /*
* Projects list rendered on dashboard and user page * Projects list rendered on dashboard and user page
*/ */
.projects-list { .projects-list {
@include basic-list; @include basic-list;
...@@ -297,9 +468,15 @@ pre.light-well { ...@@ -297,9 +468,15 @@ pre.light-well {
color: #4c4e54; color: #4c4e54;
} }
.pull-right.light { .project-controls {
float: right;
color: $gl-gray;
line-height: 45px; line-height: 45px;
color: #7f8fa4; color: #7f8fa4;
a:hover {
text-decoration: none;
}
} }
.project-description { .project-description {
...@@ -329,3 +506,8 @@ pre.light-well { ...@@ -329,3 +506,8 @@ pre.light-well {
margin-top: -1px; margin-top: -1px;
} }
} }
.inline-form {
display: inline-block;
}
...@@ -56,13 +56,19 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -56,13 +56,19 @@ class Admin::UsersController < Admin::ApplicationController
end end
def confirm def confirm
if user.confirm! if user.confirm
redirect_to :back, notice: "Successfully confirmed" redirect_to :back, notice: "Successfully confirmed"
else else
redirect_to :back, alert: "Error occurred. User was not confirmed" redirect_to :back, alert: "Error occurred. User was not confirmed"
end end
end end
def login_as
sign_in(user)
flash[:alert] = "Logged in as #{user.username}"
redirect_to root_path
end
def disable_two_factor def disable_two_factor
user.disable_two_factor! user.disable_two_factor!
redirect_to admin_user_path(user), redirect_to admin_user_path(user),
......
...@@ -117,9 +117,14 @@ class ApplicationController < ActionController::Base ...@@ -117,9 +117,14 @@ class ApplicationController < ActionController::Base
redirect_to request.original_url.gsub(/\.git\Z/, '') and return redirect_to request.original_url.gsub(/\.git\Z/, '') and return
end end
@project = Project.find_with_namespace("#{namespace}/#{id}") project_path = "#{namespace}/#{id}"
@project = Project.find_with_namespace(project_path)
if @project and can?(current_user, :read_project, @project) if @project and can?(current_user, :read_project, @project)
if @project.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
end
@project @project
elsif current_user.nil? elsif current_user.nil?
@project = nil @project = nil
......
...@@ -12,7 +12,10 @@ module Ci ...@@ -12,7 +12,10 @@ module Ci
def show def show
@builds = @runner.builds.order('id DESC').first(30) @builds = @runner.builds.order('id DESC').first(30)
@projects = Ci::Project.all @projects = Ci::Project.all
@projects = @projects.search(params[:search]) if params[:search].present? if params[:search].present?
@gl_projects = ::Project.search(params[:search])
@projects = @projects.where(gitlab_id: @gl_projects.select(:id))
end
@projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any? @projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
@projects = @projects.page(params[:page]).per(30) @projects = @projects.page(params[:page]).per(30)
end end
......
...@@ -38,7 +38,7 @@ module Ci ...@@ -38,7 +38,7 @@ module Ci
end end
def authorize_manage_builds! def authorize_manage_builds!
unless can?(current_user, :admin_project, gl_project) unless can?(current_user, :manage_builds, gl_project)
return page_404 return page_404
end end
end end
......
module Ci
class ChartsController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def show
@charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(@project)
@charts[:month] = Ci::Charts::MonthChart.new(@project)
@charts[:year] = Ci::Charts::YearChart.new(@project)
@charts[:build_times] = Ci::Charts::BuildTime.new(@project)
end
protected
def project
@project = Ci::Project.find(params[:project_id])
end
end
end
module Ci module Ci
class ProjectsController < Ci::ApplicationController class ProjectsController < Ci::ApplicationController
PROJECTS_BATCH = 100 before_action :authenticate_user!, except: [:build, :badge, :show]
before_action :authenticate_user!, except: [:build, :badge, :index, :show]
before_action :authenticate_public_page!, only: :show before_action :authenticate_public_page!, only: :show
before_action :project, only: [:build, :integration, :show, :badge, :edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml] before_action :project, only: [:build, :show, :badge, :toggle_shared_runners, :dumped_yaml]
before_action :authorize_access_project!, except: [:build, :badge, :index, :show, :new, :create, :disabled] before_action :authorize_access_project!, except: [:build, :badge, :show, :new, :disabled]
before_action :authorize_manage_project!, only: [:edit, :integration, :update, :destroy, :toggle_shared_runners, :dumped_yaml] before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :authenticate_token!, only: [:build] before_action :authenticate_token!, only: [:build]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
skip_before_action :check_enable_flag!, only: [:disabled] skip_before_action :check_enable_flag!, only: [:disabled]
...@@ -17,26 +15,6 @@ module Ci ...@@ -17,26 +15,6 @@ module Ci
def disabled def disabled
end end
def index
@limit, @offset = (params[:limit] || PROJECTS_BATCH).to_i, (params[:offset] || 0).to_i
@page = @offset == 0 ? 1 : (@offset / @limit + 1)
if current_user
@projects = ProjectListBuilder.new.execute(current_user, params[:search])
@projects = @projects.page(@page).per(@limit)
@total_count = @projects.size
end
respond_to do |format|
format.json do
pager_json("ci/projects/index", @total_count)
end
format.html
end
end
def show def show
@ref = params[:ref] @ref = params[:ref]
...@@ -45,57 +23,6 @@ module Ci ...@@ -45,57 +23,6 @@ module Ci
@commits = @commits.page(params[:page]).per(20) @commits = @commits.page(params[:page]).per(20)
end end
def integration
end
def create
project_data = OpenStruct.new(JSON.parse(params["project"]))
unless can?(current_user, :admin_project, ::Project.find(project_data.id))
return redirect_to ci_root_path, alert: 'You have to have at least master role to enable CI for this project'
end
@project = Ci::CreateProjectService.new.execute(current_user, project_data, ci_project_url(":project_id"))
if @project.persisted?
redirect_to ci_project_path(@project, show_guide: true), notice: 'Project was successfully created.'
else
redirect_to :back, alert: 'Cannot save project'
end
end
def edit
end
def update
if project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, project)
redirect_to :back, notice: 'Project was successfully updated.'
else
render action: "edit"
end
end
def destroy
project.gl_project.gitlab_ci_service.update_attributes(active: false)
project.destroy
Ci::EventService.new.remove_project(current_user, project)
redirect_to ci_projects_url
end
def build
@commit = Ci::CreateCommitService.new.execute(@project, params.dup)
if @commit && @commit.valid?
head 201
else
head 400
end
end
# Project status badge # Project status badge
# Image with build status for sha or ref # Image with build status for sha or ref
def badge def badge
...@@ -106,7 +33,8 @@ module Ci ...@@ -106,7 +33,8 @@ module Ci
def toggle_shared_runners def toggle_shared_runners
project.toggle!(:shared_runners_enabled) project.toggle!(:shared_runners_enabled)
redirect_to :back
redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
end end
def dumped_yaml def dumped_yaml
...@@ -124,12 +52,5 @@ module Ci ...@@ -124,12 +52,5 @@ module Ci
response.headers["Pragma"] = "no-cache" response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end end
def project_params
params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
:polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
:email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
{ variables_attributes: [:id, :key, :value, :_destroy] })
end
end end
end end
...@@ -11,10 +11,12 @@ module Ci ...@@ -11,10 +11,12 @@ module Ci
return head(403) unless current_user.ci_authorized_runners.include?(@runner) return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(@project.gl_project)
if @runner.assign_to(project, current_user) if @runner.assign_to(project, current_user)
redirect_to ci_project_runners_path(project) redirect_to path
else else
redirect_to ci_project_runners_path(project), alert: 'Failed adding runner to project' redirect_to path, alert: 'Failed adding runner to project'
end end
end end
...@@ -22,7 +24,7 @@ module Ci ...@@ -22,7 +24,7 @@ module Ci
runner_project = project.runner_projects.find(params[:id]) runner_project = project.runner_projects.find(params[:id])
runner_project.destroy runner_project.destroy
redirect_to ci_project_runners_path(project) redirect_to runners_path(@project.gl_project)
end end
private private
......
module Ci
class RunnersController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def index
@runners = @project.runners.order('id DESC')
@specific_runners =
Ci::Runner.specific.includes(:runner_projects).
where(Ci::RunnerProject.table_name => { project_id: current_user.authorized_projects } ).
where.not(id: @runners).order("#{Ci::Runner.table_name}.id DESC").page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
def edit
end
def update
if @runner.update_attributes(runner_params)
redirect_to edit_ci_project_runner_path(@project, @runner), notice: 'Runner was successfully updated.'
else
redirect_to edit_ci_project_runner_path(@project, @runner), alert: 'Runner was not updated.'
end
end
def destroy
if @runner.only_for?(@project)
@runner.destroy
end
redirect_to ci_project_runners_path(@project)
end
def resume
if @runner.update_attributes(active: true)
redirect_to ci_project_runners_path(@project, @runner), notice: 'Runner was successfully updated.'
else
redirect_to ci_project_runners_path(@project, @runner), alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to ci_project_runners_path(@project, @runner), notice: 'Runner was successfully updated.'
else
redirect_to ci_project_runners_path(@project, @runner), alert: 'Runner was not updated.'
end
end
def show
end
protected
def project
@project = Ci::Project.find(params[:project_id])
end
def set_runner
@runner ||= @project.runners.find(params[:id])
end
def runner_params
params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
end
end
end
module Ci
class TriggersController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def index
@triggers = @project.triggers
@trigger = Ci::Trigger.new
end
def create
@trigger = @project.triggers.new
@trigger.save
if @trigger.valid?
redirect_to ci_project_triggers_path(@project)
else
@triggers = @project.triggers.select(&:persisted?)
render :index
end
end
def destroy
trigger.destroy
redirect_to ci_project_triggers_path(@project)
end
private
def trigger
@trigger ||= @project.triggers.find(params[:id])
end
def project
@project = Ci::Project.find(params[:project_id])
end
end
end
module Ci
class VariablesController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def show
end
def update
if project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, project)
redirect_to ci_project_variables_path(project), notice: 'Variables were successfully updated.'
else
render action: 'show'
end
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
def project_params
params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
end
end
end
...@@ -4,6 +4,11 @@ class HelpController < ApplicationController ...@@ -4,6 +4,11 @@ class HelpController < ApplicationController
layout 'help' layout 'help'
def index def index
@help_index = File.read(Rails.root.join('doc', 'README.md'))
# Prefix Markdown links with `help/` unless they already have been
# See http://rubular.com/r/nwwhzH6Z8X
@help_index.gsub!(/(\]\()(?!help\/)([^\)\(]+)(\))/, '\1help/\2\3')
end end
def show def show
......
...@@ -16,27 +16,6 @@ class PasswordsController < Devise::PasswordsController ...@@ -16,27 +16,6 @@ class PasswordsController < Devise::PasswordsController
end end
end end
# After a user resets their password, prompt for 2FA code if enabled instead
# of signing in automatically
#
# See http://git.io/vURrI
def update
super do |resource|
# TODO (rspeicher): In Devise master (> 3.4.1), we can set
# `Devise.sign_in_after_reset_password = false` and avoid this mess.
if resource.errors.empty? && resource.try(:two_factor_enabled?)
resource.unlock_access! if unlockable?(resource)
# Since we are not signing this user in, we use the :updated_not_active
# message which only contains "Your password was changed successfully."
set_flash_message(:notice, :updated_not_active) if is_flashing_format?
# Redirect to sign in so they can enter 2FA code
respond_with(resource, location: new_session_path(resource)) and return
end
end
end
def edit def edit
super super
reset_password_token = Devise.token_generator.digest( reset_password_token = Devise.token_generator.digest(
......
...@@ -9,7 +9,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -9,7 +9,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end end
def create def create
if current_user.valid_otp?(params[:pin_code]) if current_user.validate_and_consume_otp!(params[:pin_code])
current_user.two_factor_enabled = true current_user.two_factor_enabled = true
@codes = current_user.generate_otp_backup_codes! @codes = current_user.generate_otp_backup_codes!
current_user.save! current_user.save!
......
...@@ -25,4 +25,14 @@ class Projects::ApplicationController < ApplicationController ...@@ -25,4 +25,14 @@ class Projects::ApplicationController < ApplicationController
) )
end end
end end
private
def ci_enabled
return render_404 unless @project.gitlab_ci?
end
def ci_project
@ci_project ||= @project.ensure_gitlab_ci_project
end
end end
class Projects::CiSettingsController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout "project_settings"
def edit
end
def update
if ci_project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, ci_project)
redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.'
else
render action: "edit"
end
end
def destroy
ci_project.destroy
Ci::EventService.new.remove_project(current_user, ci_project)
project.gitlab_ci_service.update_attributes(active: false)
redirect_to project_path(project), notice: "CI was disabled for this project"
end
protected
def project_params
params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
:polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
:email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
{ variables_attributes: [:id, :key, :value, :_destroy] })
end
end
...@@ -22,6 +22,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -22,6 +22,8 @@ class Projects::CommitController < Projects::ApplicationController
commit_id: @commit.id commit_id: @commit.id
} }
@ci_commit = project.ci_commit(commit.sha)
respond_to do |format| respond_to do |format|
format.html format.html
format.diff { render text: @commit.to_diff } format.diff { render text: @commit.to_diff }
......
...@@ -16,10 +16,12 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -16,10 +16,12 @@ class Projects::CompareController < Projects::ApplicationController
compare_result = CompareService.new. compare_result = CompareService.new.
execute(@project, head_ref, @project, base_ref) execute(@project, head_ref, @project, base_ref)
@commits = compare_result.commits if compare_result
@diffs = compare_result.diffs @commits = compare_result.commits
@commit = @commits.last @diffs = compare_result.diffs
@line_notes = [] @commit = @commits.last
@line_notes = []
end
end end
def create def create
......
...@@ -5,6 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -5,6 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :ci_enabled, only: :ci
def show def show
respond_to do |format| respond_to do |format|
...@@ -23,6 +24,16 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -23,6 +24,16 @@ class Projects::GraphsController < Projects::ApplicationController
@commits_per_month = @commits_graph.commits_per_month @commits_per_month = @commits_graph.commits_per_month
end end
def ci
ci_project = @project.gitlab_ci_project
@charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(ci_project)
@charts[:month] = Ci::Charts::MonthChart.new(ci_project)
@charts[:year] = Ci::Charts::YearChart.new(ci_project)
@charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
end
private private
def fetch_graph def fetch_graph
......
...@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits] before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
before_action :validates_merge_request, only: [:show, :diffs, :commits] before_action :validates_merge_request, only: [:show, :diffs, :commits]
before_action :define_show_vars, only: [:show, :diffs, :commits] before_action :define_show_vars, only: [:show, :diffs, :commits]
before_action :ensure_ref_fetched, only: [:show, :commits, :diffs]
# Allow read any merge_request # Allow read any merge_request
before_action :authorize_read_merge_request! before_action :authorize_read_merge_request!
...@@ -149,6 +150,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -149,6 +150,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return access_denied! unless @merge_request.can_be_merged_by?(current_user) return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.mergeable? if @merge_request.mergeable?
@merge_request.update(merge_error: nil)
MergeWorker.perform_async(@merge_request.id, current_user.id, params) MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true @status = true
else else
...@@ -257,8 +259,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -257,8 +259,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits @commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff @merge_request_diff = @merge_request.merge_request_diff
@source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
if @merge_request.locked_long_ago? if @merge_request.locked_long_ago?
@merge_request.unlock_mr @merge_request.unlock_mr
@merge_request.close @merge_request.close
...@@ -277,4 +278,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -277,4 +278,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:state_event, :description, :task_num, label_ids: [] :state_event, :description, :task_num, label_ids: []
) )
end end
# Make sure merge requests created before 8.0
# have head file in refs/merge-requests/
def ensure_ref_fetched
@merge_request.ensure_ref_fetched
end
end end
class Projects::RunnersController < Projects::ApplicationController
before_action :ci_project
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_admin_project!
layout 'project_settings'
def index
@runners = @ci_project.runners.order('id DESC')
@specific_runners =
Ci::Runner.specific.includes(:runner_projects).
where(Ci::RunnerProject.table_name => { project_id: current_user.authorized_projects } ).
where.not(id: @runners).order("#{Ci::Runner.table_name}.id DESC").page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
def edit
end
def update
if @runner.update_attributes(runner_params)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
end
end
def destroy
if @runner.only_for?(@ci_project)
@runner.destroy
end
redirect_to runners_path(@project)
end
def resume
if @runner.update_attributes(active: true)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
else
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
end
end
def show
end
protected
def set_runner
@runner ||= @ci_project.runners.find(params[:id])
end
def runner_params
params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
end
end
class Projects::TriggersController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout 'project_settings'
def index
@triggers = @ci_project.triggers
@trigger = Ci::Trigger.new
end
def create
@trigger = @ci_project.triggers.new
@trigger.save
if @trigger.valid?
redirect_to namespace_project_triggers_path(@project.namespace, @project)
else
@triggers = @ci_project.triggers.select(&:persisted?)
render :index
end
end
def destroy
trigger.destroy
redirect_to namespace_project_triggers_path(@project.namespace, @project)
end
private
def trigger
@trigger ||= @ci_project.triggers.find(params[:id])
end
end
class Projects::VariablesController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout 'project_settings'
def show
end
def update
if ci_project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, ci_project)
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
else
render action: 'show'
end
end
private
def project_params
params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
end
end
...@@ -86,6 +86,10 @@ class ProjectsController < ApplicationController ...@@ -86,6 +86,10 @@ class ProjectsController < ApplicationController
if @project.empty_repo? if @project.empty_repo?
render 'projects/empty' render 'projects/empty'
else else
if current_user
@membership = @project.project_member_by_id(current_user.id)
end
render :show render :show
end end
else else
......
...@@ -22,6 +22,10 @@ class RootController < Dashboard::ProjectsController ...@@ -22,6 +22,10 @@ class RootController < Dashboard::ProjectsController
when 'stars' when 'stars'
flash.keep flash.keep
redirect_to starred_dashboard_projects_path redirect_to starred_dashboard_projects_path
when 'project_activity'
redirect_to activity_dashboard_path
when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred')
else else
return return
end end
......
...@@ -99,7 +99,7 @@ class SessionsController < Devise::SessionsController ...@@ -99,7 +99,7 @@ class SessionsController < Devise::SessionsController
end end
def valid_otp_attempt?(user) def valid_otp_attempt?(user)
user.valid_otp?(user_params[:otp_attempt]) || user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt]) user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end end
......
module AuthHelper module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos', 'crowd'].freeze FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled? def ldap_enabled?
Gitlab.config.ldap.enabled Gitlab.config.ldap.enabled
......
module BuildsHelper
def build_ref_link build
gitlab_ref_link build.project, build.ref
end
def build_compare_link build
gitlab_compare_link build.project, build.commit.short_before_sha, build.short_sha
end
def build_commit_link build
gitlab_commit_link build.project, build.short_sha
end
def build_url(build)
ci_project_build_url(build.project, build)
end
end
module Ci
module ApplicationHelper
def loader_html
image_tag 'ci/loader.gif', alt: 'Loading'
end
def date_from_to(from, to)
"#{from.to_s(:short)} - #{to.to_s(:short)}"
end
def duration_in_words(finished_at, started_at)
if finished_at && started_at
interval_in_seconds = finished_at.to_i - started_at.to_i
elsif started_at
interval_in_seconds = Time.now.to_i - started_at.to_i
end
time_interval_in_words(interval_in_seconds)
end
def time_interval_in_words(interval_in_seconds)
minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60
if minutes >= 1
"#{pluralize(minutes, "minute")} #{pluralize(seconds, "second")}"
else
"#{pluralize(seconds, "second")}"
end
end
end
end
module Ci
module BuildsHelper
def build_ref_link build
gitlab_ref_link build.project, build.ref
end
def build_compare_link build
gitlab_compare_link build.project, build.commit.short_before_sha, build.short_sha
end
def build_commit_link build
gitlab_commit_link build.project, build.short_sha
end
def build_url(build)
ci_project_build_url(build.project, build)
end
def build_status_alert_class(build)
if build.success?
'alert-success'
elsif build.failed?
'alert-danger'
elsif build.canceled?
'alert-disabled'
else
'alert-warning'
end
end
def build_icon_css_class(build)
if build.success?
'fa-circle cgreen'
elsif build.failed?
'fa-circle cred'
else
'fa-circle light'
end
end
end
end
module Ci module Ci
module CommitsHelper module CommitsHelper
def commit_status_alert_class(commit)
return 'alert-info' unless commit
case commit.status
when 'success'
'alert-success'
when 'failed', 'canceled'
'alert-danger'
when 'skipped'
'alert-disabled'
else
'alert-warning'
end
end
def ci_commit_path(commit) def ci_commit_path(commit)
ci_project_ref_commits_path(commit.project, commit.ref, commit.sha) ci_project_ref_commits_path(commit.project, commit.ref, commit.sha)
end end
......
...@@ -27,9 +27,9 @@ module Ci ...@@ -27,9 +27,9 @@ module Ci
commits = project.commits commits = project.commits
if commits.any? && commits.last.push_data[:ci_yaml_file] if commits.any? && commits.last.push_data[:ci_yaml_file]
"#{@project.gitlab_url}/edit/master/.gitlab-ci.yml" "#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
else else
"#{@project.gitlab_url}/new/master" "#{project.gitlab_url}/new/master"
end end
end end
end end
......
module Ci
module IconsHelper
def boolean_to_icon(value)
if value.to_s == "true"
content_tag :i, nil, class: 'fa fa-circle cgreen'
else
content_tag :i, nil, class: 'fa fa-power-off clgray'
end
end
end
end
module Ci
module RoutesHelper
class Base
include Gitlab::Application.routes.url_helpers
def default_url_options
{
host: Settings.gitlab['host'],
protocol: Settings.gitlab['https'] ? "https" : "http",
port: Settings.gitlab['port']
}
end
end
def url_helpers
@url_helpers ||= Base.new
end
def self.method_missing(method, *args, &block)
@url_helpers ||= Base.new
if @url_helpers.respond_to?(method)
@url_helpers.send(method, *args, &block)
else
super method, *args, &block
end
end
end
end
module Ci
module RunnersHelper
def runner_status_icon(runner)
unless runner.contacted_at
return content_tag :i, nil,
class: "fa fa-warning-sign",
title: "New runner. Has not connected yet"
end
status =
if runner.active?
runner.contacted_at > 3.hour.ago ? :online : :offline
else
:paused
end
content_tag :i, nil,
class: "fa fa-circle runner-status-#{status}",
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end
end
end
module Ci
module TriggersHelper
def ci_build_trigger_url(project_id, ref_name)
"#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger"
end
end
end
module Ci
module UserHelper
def user_avatar_url(user = nil, size = nil, default = 'identicon')
size = 40 if size.nil? || size <= 0
if user.blank? || user.avatar_url.blank?
'ci/no_avatar.png'
elsif /^(http(s?):\/\/(www|secure)\.gravatar\.com\/avatar\/(\w*))/ =~ user.avatar_url
Regexp.last_match[0] + "?s=#{size}&d=#{default}"
else
user.avatar_url
end
end
end
end
module CiStatusHelper
def ci_status_path(ci_commit)
ci_project_ref_commits_path(ci_commit.project, ci_commit.ref, ci_commit)
end
def ci_status_icon(ci_commit)
ci_icon_for_status(ci_commit.status)
end
def ci_status_color(ci_commit)
case ci_commit.status
when 'success'
'green'
when 'failed'
'red'
when 'running', 'pending'
'yellow'
else
'gray'
end
end
def ci_status_with_icon(status)
content_tag :span, class: "ci-status ci-#{status}" do
ci_icon_for_status(status) + '&nbsp;'.html_safe + status
end
end
def ci_icon_for_status(status)
icon_name =
case status
when 'success'
'check'
when 'failed'
'close'
when 'running', 'pending'
'clock-o'
else
'circle'
end
icon(icon_name)
end
end
...@@ -135,7 +135,7 @@ module CommitsHelper ...@@ -135,7 +135,7 @@ module CommitsHelper
# size: size of the avatar image in px # size: size of the avatar image in px
def commit_person_link(commit, options = {}) def commit_person_link(commit, options = {})
user = commit.send(options[:source]) user = commit.send(options[:source])
source_name = clean(commit.send "#{options[:source]}_name".to_sym) source_name = clean(commit.send "#{options[:source]}_name".to_sym)
source_email = clean(commit.send "#{options[:source]}_email".to_sym) source_email = clean(commit.send "#{options[:source]}_email".to_sym)
......
...@@ -167,4 +167,23 @@ module DiffHelper ...@@ -167,4 +167,23 @@ module DiffHelper
content_tag(:span, commit_id, class: 'monospace'), content_tag(:span, commit_id, class: 'monospace'),
].join(' ').html_safe ].join(' ').html_safe
end end
def commit_for_diff(diff)
if diff.deleted_file
@merge_request ? @merge_request.commits.last : @commit.parent_id
else
@commit
end
end
def diff_file_html_data(project, diff_commit, diff_file)
{
blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
tree_join(diff_commit.id, diff_file.file_path))
}
end
def editable_diff?(diff)
!diff.deleted_file && @merge_request && @merge_request.source_project
end
end end
...@@ -45,7 +45,7 @@ module GitlabMarkdownHelper ...@@ -45,7 +45,7 @@ module GitlabMarkdownHelper
end end
def markdown(text, context = {}) def markdown(text, context = {})
context.merge!( context.reverse_merge!(
current_user: current_user, current_user: current_user,
path: @path, path: @path,
project: @project, project: @project,
...@@ -59,7 +59,7 @@ module GitlabMarkdownHelper ...@@ -59,7 +59,7 @@ module GitlabMarkdownHelper
# TODO (rspeicher): Remove all usages of this helper and just call `markdown` # TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered # with a custom pipeline depending on the content being rendered
def gfm(text, options = {}) def gfm(text, options = {})
options.merge!( options.reverse_merge!(
current_user: current_user, current_user: current_user,
path: @path, path: @path,
project: @project, project: @project,
...@@ -165,7 +165,7 @@ module GitlabMarkdownHelper ...@@ -165,7 +165,7 @@ module GitlabMarkdownHelper
# and return true. Otherwise return false. # and return true. Otherwise return false.
def truncate_if_block(node, truncated) def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated if node.element? && node.description.block? && !truncated
node.content = "#{node.content}..." if node.next_sibling node.inner_html = "#{node.inner_html}..." if node.next_sibling
true true
else else
truncated truncated
......
...@@ -33,6 +33,14 @@ module GitlabRoutingHelper ...@@ -33,6 +33,14 @@ module GitlabRoutingHelper
edit_namespace_project_path(project.namespace, project, *args) edit_namespace_project_path(project.namespace, project, *args)
end end
def runners_path(project, *args)
namespace_project_runners_path(project.namespace, project, *args)
end
def runner_path(runner, *args)
namespace_project_runner_path(@project.namespace, @project, runner, *args)
end
def issue_path(entity, *args) def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end end
......
module GraphHelper module GraphHelper
def get_refs(repo, commit) def get_refs(repo, commit)
refs = "" refs = ""
refs << commit.ref_names(repo).join(' ') # Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX)
# so anything leftover is internally used by GitLab
commit_refs = commit.ref_names(repo).reject{ |name| name.starts_with?('refs/') }
refs << commit_refs.join(' ')
# append note count # append note count
refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
......
...@@ -71,4 +71,17 @@ module MergeRequestsHelper ...@@ -71,4 +71,17 @@ module MergeRequestsHelper
merge_request.source_branch merge_request.source_branch
end end
end end
def format_mr_branch_names(merge_request)
source_path = merge_request.source_project_path
target_path = merge_request.target_project_path
source_branch = merge_request.source_branch
target_branch = merge_request.target_branch
if source_path == target_path
[source_branch, target_branch]
else
["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"]
end
end
end end
...@@ -12,4 +12,49 @@ module NotificationsHelper ...@@ -12,4 +12,49 @@ module NotificationsHelper
icon('circle-o', class: 'ns-default') icon('circle-o', class: 'ns-default')
end end
end end
def notification_list_item(notification_level, user_membership)
case notification_level
when Notification::N_DISABLED
content_tag(:li, class: active_level_for(user_membership, Notification::N_DISABLED)) do
link_to '#', class: 'update-notification', data: { notification_level: Notification::N_DISABLED } do
icon('microphone-slash fw', text: 'Disabled')
end
end
when Notification::N_PARTICIPATING
content_tag(:li, class: active_level_for(user_membership, Notification::N_PARTICIPATING)) do
link_to '#', class: 'update-notification', data: { notification_level: Notification::N_PARTICIPATING } do
icon('volume-up fw', text: 'Participate')
end
end
when Notification::N_WATCH
content_tag(:li, class: active_level_for(user_membership, Notification::N_WATCH)) do
link_to '#', class: 'update-notification', data: { notification_level: Notification::N_WATCH } do
icon('eye fw', text: 'Watch')
end
end
when Notification::N_MENTION
content_tag(:li, class: active_level_for(user_membership, Notification::N_MENTION)) do
link_to '#', class: 'update-notification', data: { notification_level: Notification::N_MENTION } do
icon('at fw', text: 'On mention')
end
end
when Notification::N_GLOBAL
content_tag(:li, class: active_level_for(user_membership, Notification::N_GLOBAL)) do
link_to '#', class: 'update-notification', data: { notification_level: Notification::N_GLOBAL } do
icon('globe fw', text: 'Global')
end
end
else
# do nothing
end
end
def notification_label(user_membership)
Notification.new(user_membership).to_s
end
def active_level_for(user_membership, level)
'active' if user_membership.notification_level == level
end
end end
...@@ -3,7 +3,9 @@ module PreferencesHelper ...@@ -3,7 +3,9 @@ module PreferencesHelper
# Maps `dashboard` values to more user-friendly option text # Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = { DASHBOARD_CHOICES = {
projects: 'Your Projects (default)', projects: 'Your Projects (default)',
stars: 'Starred Projects' stars: 'Starred Projects',
project_activity: "Your Projects' Activity",
starred_project_activity: "Starred Projects' Activity"
}.with_indifferent_access.freeze }.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text # Returns an Array usable by a select field for more user-friendly option text
......
...@@ -156,8 +156,8 @@ module ProjectsHelper ...@@ -156,8 +156,8 @@ module ProjectsHelper
end end
end end
def repository_size(project = nil) def repository_size(project = @project)
"#{(project || @project).repository_size} MB" "#{project.repository_size} MB"
rescue rescue
# In order to prevent 500 error # In order to prevent 500 error
# when application cannot allocate memory # when application cannot allocate memory
......
module RunnersHelper
def runner_status_icon(runner)
unless runner.contacted_at
return content_tag :i, nil,
class: "fa fa-warning-sign",
title: "New runner. Has not connected yet"
end
status =
if runner.active?
runner.contacted_at > 3.hour.ago ? :online : :offline
else
:paused
end
content_tag :i, nil,
class: "fa fa-circle runner-status-#{status}",
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end
end
module TimeHelper
def duration_in_words(finished_at, started_at)
if finished_at && started_at
interval_in_seconds = finished_at.to_i - started_at.to_i
elsif started_at
interval_in_seconds = Time.now.to_i - started_at.to_i
end
time_interval_in_words(interval_in_seconds)
end
def time_interval_in_words(interval_in_seconds)
minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60
if minutes >= 1
"#{pluralize(minutes, "minute")} #{pluralize(seconds, "second")}"
else
"#{pluralize(seconds, "second")}"
end
end
def date_from_to(from, to)
"#{from.to_s(:short)} - #{to.to_s(:short)}"
end
end
module TriggersHelper
def ci_build_trigger_url(project_id, ref_name)
"#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger"
end
end
module VersionCheckHelper module VersionCheckHelper
def version_status_badge def version_status_badge
if Rails.env.production? if Rails.env.production? && current_application_settings.version_check_enabled
image_tag VersionCheck.new.url image_tag VersionCheck.new.url
end end
end end
......
...@@ -2,7 +2,6 @@ module Ci ...@@ -2,7 +2,6 @@ module Ci
class Notify < ActionMailer::Base class Notify < ActionMailer::Base
include Ci::Emails::Builds include Ci::Emails::Builds
add_template_helper Ci::ApplicationHelper
add_template_helper Ci::GitlabHelper add_template_helper Ci::GitlabHelper
default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:host] = Gitlab.config.gitlab.host
......
...@@ -12,7 +12,7 @@ module Emails ...@@ -12,7 +12,7 @@ module Emails
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@commit.title} (#{@commit.short_id})")) subject: subject("#{@commit.title} (#{@commit.short_id})"))
SentNotification.record(@commit, recipient_id, reply_key) SentNotification.record_note(@note, recipient_id, reply_key)
end end
def note_issue_email(recipient_id, note_id) def note_issue_email(recipient_id, note_id)
...@@ -27,7 +27,7 @@ module Emails ...@@ -27,7 +27,7 @@ module Emails
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key) SentNotification.record_note(@note, recipient_id, reply_key)
end end
def note_merge_request_email(recipient_id, note_id) def note_merge_request_email(recipient_id, note_id)
...@@ -43,7 +43,7 @@ module Emails ...@@ -43,7 +43,7 @@ module Emails
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key) SentNotification.record_note(@note, recipient_id, reply_key)
end end
end end
end end
...@@ -50,10 +50,11 @@ module Emails ...@@ -50,10 +50,11 @@ module Emails
subject: subject("Invitation declined")) subject: subject("Invitation declined"))
end end
def project_was_moved_email(project_id, user_id) def project_was_moved_email(project_id, user_id, old_path_with_namespace)
@current_user = @user = User.find user_id @current_user = @user = User.find user_id
@project = Project.find project_id @project = Project.find project_id
@target_url = namespace_project_url(@project.namespace, @project) @target_url = namespace_project_url(@project.namespace, @project)
@old_path_with_namespace = old_path_with_namespace
mail(to: @user.notification_email, mail(to: @user.notification_email,
subject: subject("Project was moved")) subject: subject("Project was moved"))
end end
......
...@@ -110,7 +110,7 @@ class Notify < BaseMailer ...@@ -110,7 +110,7 @@ class Notify < BaseMailer
if reply_key if reply_key
headers['X-GitLab-Reply-Key'] = reply_key headers['X-GitLab-Reply-Key'] = reply_key
address = Mail::Address.new(Gitlab::ReplyByEmail.reply_address(reply_key)) address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace address.display_name = @project.name_with_namespace
headers['Reply-To'] = address headers['Reply-To'] = address
...@@ -150,6 +150,6 @@ class Notify < BaseMailer ...@@ -150,6 +150,6 @@ class Notify < BaseMailer
end end
def reply_key def reply_key
@reply_key ||= Gitlab::ReplyByEmail.reply_key @reply_key ||= SentNotification.reply_key
end end
end end
...@@ -30,7 +30,6 @@ module Ci ...@@ -30,7 +30,6 @@ module Ci
LAZY_ATTRIBUTES = ['trace'] LAZY_ATTRIBUTES = ['trace']
belongs_to :commit, class_name: 'Ci::Commit' belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :project, class_name: 'Ci::Project'
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
...@@ -80,7 +79,6 @@ module Ci ...@@ -80,7 +79,6 @@ module Ci
new_build.commands = build.commands new_build.commands = build.commands
new_build.tag_list = build.tag_list new_build.tag_list = build.tag_list
new_build.commit_id = build.commit_id new_build.commit_id = build.commit_id
new_build.project_id = build.project_id
new_build.name = build.name new_build.name = build.name
new_build.allow_failure = build.allow_failure new_build.allow_failure = build.allow_failure
new_build.stage = build.stage new_build.stage = build.stage
...@@ -137,7 +135,7 @@ module Ci ...@@ -137,7 +135,7 @@ module Ci
state :canceled, value: 'canceled' state :canceled, value: 'canceled'
end end
delegate :sha, :short_sha, :before_sha, :ref, delegate :sha, :short_sha, :before_sha, :ref, :project,
to: :commit, prefix: false to: :commit, prefix: false
def trace_html def trace_html
...@@ -188,7 +186,7 @@ module Ci ...@@ -188,7 +186,7 @@ module Ci
end end
def project_id def project_id
commit.project_id commit.project.id
end end
def project_name def project_name
......
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
module Ci module Ci
class Commit < ActiveRecord::Base class Commit < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :project, class_name: 'Ci::Project' belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
has_many :builds, dependent: :destroy, class_name: 'Ci::Build' has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
...@@ -36,6 +36,14 @@ module Ci ...@@ -36,6 +36,14 @@ module Ci
sha sha
end end
def project
@project ||= gl_project.ensure_gitlab_ci_project
end
def project_id
project.id
end
def last_build def last_build
builds.order(:id).last builds.order(:id).last
end end
...@@ -111,15 +119,14 @@ module Ci ...@@ -111,15 +119,14 @@ module Ci
builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag) builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs| builds_attrs.map do |build_attrs|
builds.create!({ builds.create!({
project: project, name: build_attrs[:name],
name: build_attrs[:name], commands: build_attrs[:script],
commands: build_attrs[:script], tag_list: build_attrs[:tags],
tag_list: build_attrs[:tags], options: build_attrs[:options],
options: build_attrs[:options], allow_failure: build_attrs[:allow_failure],
allow_failure: build_attrs[:allow_failure], stage: build_attrs[:stage],
stage: build_attrs[:stage], trigger_request: trigger_request,
trigger_request: trigger_request, })
})
end end
end end
...@@ -236,7 +243,7 @@ module Ci ...@@ -236,7 +243,7 @@ module Ci
end end
def config_processor def config_processor
@config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file] || project.generated_yaml_config) @config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file])
rescue Ci::GitlabCiYamlProcessor::ValidationError => e rescue Ci::GitlabCiYamlProcessor::ValidationError => e
save_yaml_error(e.message) save_yaml_error(e.message)
nil nil
......
...@@ -33,8 +33,6 @@ module Ci ...@@ -33,8 +33,6 @@ module Ci
belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
has_many :commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit'
has_many :builds, through: :commits, dependent: :destroy, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, class_name: 'Ci::Runner' has_many :runners, through: :runner_projects, class_name: 'Ci::Runner'
has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook' has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
...@@ -50,19 +48,18 @@ module Ci ...@@ -50,19 +48,18 @@ module Ci
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
# #
# Validations # Validations
# #
validates_presence_of :name, :timeout, :token, :default_ref, validates_presence_of :timeout, :token, :default_ref, :gitlab_id
:path, :ssh_url_to_repo, :gitlab_id
validates_uniqueness_of :gitlab_id validates_uniqueness_of :gitlab_id
validates :polling_interval, validates :polling_interval,
presence: true, presence: true,
if: ->(project) { project.always_build.present? } if: ->(project) { project.always_build.present? }
scope :public_only, ->() { where(public: true) }
before_validation :set_default_values before_validation :set_default_values
...@@ -78,11 +75,8 @@ module Ci ...@@ -78,11 +75,8 @@ module Ci
def parse(project) def parse(project)
params = { params = {
name: project.name_with_namespace,
gitlab_id: project.id, gitlab_id: project.id,
path: project.path_with_namespace,
default_ref: project.default_branch || 'master', default_ref: project.default_branch || 'master',
ssh_url_to_repo: project.ssh_url_to_repo,
email_add_pusher: current_application_settings.add_pusher, email_add_pusher: current_application_settings.add_pusher,
email_only_broken_builds: current_application_settings.all_broken_builds, email_only_broken_builds: current_application_settings.all_broken_builds,
} }
...@@ -92,21 +86,6 @@ module Ci ...@@ -92,21 +86,6 @@ module Ci
project project
end end
# TODO: remove
def from_gitlab(user, scope = :owned, options)
opts = user.authenticate_options
opts.merge! options
raise 'Implement me of fix'
#projects = Ci::Network.new.projects(opts.compact, scope)
if projects
projects.map { |pr| OpenStruct.new(pr) }
else
[]
end
end
def already_added?(project) def already_added?(project)
where(gitlab_id: project.id).any? where(gitlab_id: project.id).any?
end end
...@@ -114,19 +93,26 @@ module Ci ...@@ -114,19 +93,26 @@ module Ci
def unassigned(runner) def unassigned(runner)
joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \ joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \
"AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}"). "AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}").
where('#{Ci::RunnerProject.table_name}.project_id' => nil) where("#{Ci::RunnerProject.table_name}.project_id" => nil)
end end
def ordered_by_last_commit_date def ordered_by_last_commit_date
last_commit_subquery = "(SELECT project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY project_id)" last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)"
joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.id = last_commit.project_id"). joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC") order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
end end
end
def search(query) def name
where("LOWER(#{Ci::Project.table_name}.name) LIKE :query", name_with_namespace
query: "%#{query.try(:downcase)}%") end
end
def path
path_with_namespace
end
def gitlab_url
web_url
end end
def any_runners? def any_runners?
...@@ -139,10 +125,11 @@ module Ci ...@@ -139,10 +125,11 @@ module Ci
def set_default_values def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank? self.token = SecureRandom.hex(15) if self.token.blank?
self.default_ref ||= 'master'
end end
def tracked_refs def tracked_refs
@tracked_refs ||= default_ref.split(",").map{|ref| ref.strip} @tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
end end
def valid_token? token def valid_token? token
...@@ -182,7 +169,7 @@ module Ci ...@@ -182,7 +169,7 @@ module Ci
# using http and basic auth # using http and basic auth
def repo_url_with_auth def repo_url_with_auth
auth = "gitlab-ci-token:#{token}@" auth = "gitlab-ci-token:#{token}@"
url = gitlab_url + ".git" url = http_url_to_repo + ".git"
url.sub(/^https?:\/\//) do |prefix| url.sub(/^https?:\/\//) do |prefix|
prefix + auth prefix + auth
end end
...@@ -214,12 +201,16 @@ module Ci ...@@ -214,12 +201,16 @@ module Ci
end end
end end
def gitlab_url
File.join(Gitlab.config.gitlab.url, path)
end
def setup_finished? def setup_finished?
commits.any? commits.any?
end end
def commits
gl_project.ci_commits
end
def builds
gl_project.ci_builds
end
end end
end end
...@@ -41,6 +41,10 @@ module Ci ...@@ -41,6 +41,10 @@ module Ci
query: "%#{query.try(:downcase)}%") query: "%#{query.try(:downcase)}%")
end end
def gl_projects_ids
projects.select(:gitlab_id)
end
def set_default_values def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank? self.token = SecureRandom.hex(15) if self.token.blank?
end end
......
...@@ -433,10 +433,22 @@ class MergeRequest < ActiveRecord::Base ...@@ -433,10 +433,22 @@ class MergeRequest < ActiveRecord::Base
target_project.repository.fetch_ref( target_project.repository.fetch_ref(
source_project.repository.path_to_repo, source_project.repository.path_to_repo,
"refs/heads/#{source_branch}", "refs/heads/#{source_branch}",
"refs/merge-requests/#{iid}/head" ref_path
) )
end end
def ref_path
"refs/merge-requests/#{iid}/head"
end
def ref_is_fetched?
File.exists?(File.join(project.repository.path_to_repo, ref_path))
end
def ensure_ref_fetched
fetch_ref unless ref_is_fetched?
end
def in_locked_state def in_locked_state
begin begin
lock_mr lock_mr
......
...@@ -123,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -123,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base
if new_diffs.any? if new_diffs.any?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
self.state = :overflow_diff_files_limit self.state = :overflow_diff_files_limit
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES] new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
end end
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
self.state = :overflow_diff_lines_limit self.state = :overflow_diff_lines_limit
new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES] new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
end end
end end
......
...@@ -137,7 +137,9 @@ class Namespace < ActiveRecord::Base ...@@ -137,7 +137,9 @@ class Namespace < ActiveRecord::Base
end end
def send_update_instructions def send_update_instructions
projects.each(&:send_move_instructions) projects.each do |project|
project.send_move_instructions("#{path_was}/#{project.path}")
end
end end
def kind def kind
......
...@@ -12,7 +12,7 @@ class Notification ...@@ -12,7 +12,7 @@ class Notification
class << self class << self
def notification_levels def notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION] [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH]
end end
def options_with_labels def options_with_labels
...@@ -26,7 +26,7 @@ class Notification ...@@ -26,7 +26,7 @@ class Notification
end end
def project_notification_levels def project_notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION] [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH, N_GLOBAL]
end end
end end
...@@ -57,4 +57,21 @@ class Notification ...@@ -57,4 +57,21 @@ class Notification
def level def level
target.notification_level target.notification_level
end end
def to_s
case level
when N_DISABLED
'Disabled'
when N_PARTICIPATING
'Participating'
when N_WATCH
'Watching'
when N_MENTION
'On mention'
when N_GLOBAL
'Global'
else
# do nothing
end
end
end end
...@@ -39,6 +39,7 @@ class Project < ActiveRecord::Base ...@@ -39,6 +39,7 @@ class Project < ActiveRecord::Base
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Referable include Referable
include Sortable include Sortable
include AfterCommitQueue
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Enumerize extend Enumerize
...@@ -117,6 +118,8 @@ class Project < ActiveRecord::Base ...@@ -117,6 +118,8 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user has_many :starrers, through: :users_star_projects, source: :user
has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
...@@ -191,7 +194,7 @@ class Project < ActiveRecord::Base ...@@ -191,7 +194,7 @@ class Project < ActiveRecord::Base
state :finished state :finished
state :failed state :failed
after_transition any => :started, do: :add_import_job after_transition any => :started, do: :schedule_add_import_job
after_transition any => :finished, do: :clear_import_data after_transition any => :finished, do: :clear_import_data
end end
...@@ -235,10 +238,10 @@ class Project < ActiveRecord::Base ...@@ -235,10 +238,10 @@ class Project < ActiveRecord::Base
return nil unless id.include?('/') return nil unless id.include?('/')
id = id.split('/') id = id.split('/')
namespace = Namespace.find_by(path: id.first) namespace = Namespace.by_path(id.first)
return nil unless namespace return nil unless namespace
where(namespace_id: namespace.id).find_by(path: id.second) where(namespace_id: namespace.id).where("LOWER(projects.path) = :path", path: id.second.downcase).first
end end
def visibility_levels def visibility_levels
...@@ -275,13 +278,17 @@ class Project < ActiveRecord::Base ...@@ -275,13 +278,17 @@ class Project < ActiveRecord::Base
id && persisted? id && persisted?
end end
def schedule_add_import_job
run_after_commit(:add_import_job)
end
def add_import_job def add_import_job
if forked? if forked?
unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path) unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path)
import_fail import_fail
end end
else else
RepositoryImportWorker.perform_in(2.seconds, id) RepositoryImportWorker.perform_async(id)
end end
end end
...@@ -428,7 +435,7 @@ class Project < ActiveRecord::Base ...@@ -428,7 +435,7 @@ class Project < ActiveRecord::Base
end end
def gitlab_ci? def gitlab_ci?
gitlab_ci_service && gitlab_ci_service.active gitlab_ci_service && gitlab_ci_service.active && gitlab_ci_project.present?
end end
def ci_services def ci_services
...@@ -474,8 +481,8 @@ class Project < ActiveRecord::Base ...@@ -474,8 +481,8 @@ class Project < ActiveRecord::Base
end end
end end
def send_move_instructions def send_move_instructions(old_path_with_namespace)
NotificationService.new.project_was_moved(self) NotificationService.new.project_was_moved(self, old_path_with_namespace)
end end
def owner def owner
...@@ -617,7 +624,7 @@ class Project < ActiveRecord::Base ...@@ -617,7 +624,7 @@ class Project < ActiveRecord::Base
# So we basically we mute exceptions in next actions # So we basically we mute exceptions in next actions
begin begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions send_move_instructions(old_path_with_namespace)
reset_events_cache reset_events_cache
rescue rescue
# Returning false does not rollback after_* transaction but gives # Returning false does not rollback after_* transaction but gives
...@@ -735,4 +742,22 @@ class Project < ActiveRecord::Base ...@@ -735,4 +742,22 @@ class Project < ActiveRecord::Base
errors.add(:base, 'Failed create wiki') errors.add(:base, 'Failed create wiki')
false false
end end
def ci_commit(sha)
gitlab_ci_project.commits.find_by(sha: sha) if gitlab_ci?
end
def ensure_gitlab_ci_project
gitlab_ci_project || create_gitlab_ci_project
end
def enable_ci(user)
# Enable service
service = gitlab_ci_service || create_gitlab_ci_service
service.active = true
service.save
# Create Ci::Project
Ci::CreateProjectService.new.execute(user, self)
end
end end
...@@ -69,14 +69,6 @@ class BuildkiteService < CiService ...@@ -69,14 +69,6 @@ class BuildkiteService < CiService
"#{project_url}/builds?commit=#{sha}" "#{project_url}/builds?commit=#{sha}"
end end
def builds_path
"#{project_url}/builds?branch=#{project.default_branch}"
end
def status_img_path
"#{buildkite_endpoint('badge')}/#{status_token}.svg"
end
def title def title
'Buildkite' 'Buildkite'
end end
......
module Ci module Ci
class HipChatMessage class HipChatMessage
include Gitlab::Application.routes.url_helpers
attr_reader :build attr_reader :build
def initialize(build) def initialize(build)
...@@ -8,13 +10,13 @@ module Ci ...@@ -8,13 +10,13 @@ module Ci
def to_s def to_s
lines = Array.new lines = Array.new
lines.push("<a href=\"#{Ci::RoutesHelper.ci_project_url(project)}\">#{project.name}</a> - ") lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
if commit.matrix? if commit.matrix?
lines.push("<a href=\"#{Ci::RoutesHelper.ci_project_ref_commits_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>") lines.push("<a href=\"#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>")
else else
first_build = commit.builds_without_retry.first first_build = commit.builds_without_retry.first
lines.push("<a href=\"#{Ci::RoutesHelper.ci_project_build_url(project, first_build)}\">Build '#{first_build.name}' ##{first_build.id}</a></br>") lines.push("<a href=\"#{ci_project_build_url(project, first_build)}\">Build '#{first_build.name}' ##{first_build.id}</a></br>")
end end
lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>") lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
......
...@@ -2,6 +2,8 @@ require 'slack-notifier' ...@@ -2,6 +2,8 @@ require 'slack-notifier'
module Ci module Ci
class SlackMessage class SlackMessage
include Gitlab::Application.routes.url_helpers
def initialize(commit) def initialize(commit)
@commit = commit @commit = commit
end end
...@@ -27,7 +29,7 @@ module Ci ...@@ -27,7 +29,7 @@ module Ci
next unless build.failed? next unless build.failed?
fields << { fields << {
title: build.name, title: build.name,
value: "Build <#{Ci::RoutesHelper.ci_project_build_url(project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)." value: "Build <#{ci_project_build_url(project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
} }
end end
end end
...@@ -44,12 +46,12 @@ module Ci ...@@ -44,12 +46,12 @@ module Ci
attr_reader :commit attr_reader :commit
def attachment_message def attachment_message
out = "<#{Ci::RoutesHelper.ci_project_url(project)}|#{project_name}>: " out = "<#{ci_project_url(project)}|#{project_name}>: "
if commit.matrix? if commit.matrix?
out << "Commit <#{Ci::RoutesHelper.ci_project_ref_commits_url(project, commit.ref, commit.sha)}|\##{commit.id}> " out << "Commit <#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}|\##{commit.id}> "
else else
build = commit.builds_without_retry.first build = commit.builds_without_retry.first
out << "Build <#{Ci::RoutesHelper.ci_project_build_path(project, build)}|\##{build.id}> " out << "Build <#{ci_project_build_url(project, build)}|\##{build.id}> "
end end
out << "(<#{commit_sha_link}|#{commit.short_sha}>) " out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
out << "of <#{commit_ref_link}|#{commit.ref}> " out << "of <#{commit_ref_link}|#{commit.ref}> "
......
...@@ -26,7 +26,7 @@ class DroneCiService < CiService ...@@ -26,7 +26,7 @@ class DroneCiService < CiService
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated? format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
validates :token, validates :token,
presence: true, presence: true,
format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated? if: :activated?
after_save :compose_service_hook, if: :activated? after_save :compose_service_hook, if: :activated?
...@@ -135,20 +135,6 @@ class DroneCiService < CiService ...@@ -135,20 +135,6 @@ class DroneCiService < CiService
commit_page(sha, ref) commit_page(sha, ref)
end end
def builds_path
url = [drone_url, "#{project.namespace.path}/#{project.path}"]
URI.join(*url).to_s
end
def status_img_path
url = [drone_url,
"api/badges/#{project.namespace.path}/#{project.path}/status.svg",
"?branch=#{URI::encode(project.default_branch)}"]
URI.join(*url).to_s
end
def title def title
'Drone CI' 'Drone CI'
end end
......
...@@ -19,22 +19,12 @@ ...@@ -19,22 +19,12 @@
# #
class GitlabCiService < CiService class GitlabCiService < CiService
API_PREFIX = "api/v1" include Gitlab::Application.routes.url_helpers
prop_accessor :project_url, :token, :enable_ssl_verification
validates :project_url,
presence: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
validates :token,
presence: true,
format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated?
after_save :compose_service_hook, if: :activated? after_save :compose_service_hook, if: :activated?
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.url = [project_url, "/build", "?token=#{token}"].join("")
hook.enable_ssl_verification = enable_ssl_verification
hook.save hook.save
end end
...@@ -55,71 +45,53 @@ class GitlabCiService < CiService ...@@ -55,71 +45,53 @@ class GitlabCiService < CiService
end end
end end
service_hook.execute(data) ci_project = Ci::Project.find_by(gitlab_id: project.id)
if ci_project
Ci::CreateCommitService.new.execute(ci_project, data)
end
end end
def commit_status_path(sha, ref) def token
URI::encode(project_url + "/refs/#{ref}/commits/#{sha}/status.json?token=#{token}") if project.gitlab_ci_project.present?
project.gitlab_ci_project.token
end
end end
def get_ci_build(sha, ref) def get_ci_commit(sha, ref)
@ci_builds ||= {} Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha_and_ref!(sha, ref)
@ci_builds[sha] ||= HTTParty.get(commit_status_path(sha, ref), verify: false)
end end
def commit_status(sha, ref) def commit_status(sha, ref)
response = get_ci_build(sha, ref) get_ci_commit(sha, ref).status
rescue ActiveRecord::RecordNotFound
if response.code == 200 and response["status"]
response["status"]
else
:error
end
rescue Errno::ECONNREFUSED
:error :error
end end
def fork_registration(new_project, private_token) def fork_registration(new_project, current_user)
params = { params = OpenStruct.new({
id: new_project.id, id: new_project.id,
name_with_namespace: new_project.name_with_namespace, default_branch: new_project.default_branch
path_with_namespace: new_project.path_with_namespace, })
web_url: new_project.web_url,
default_branch: new_project.default_branch, ci_project = Ci::Project.find_by!(gitlab_id: project.id)
ssh_url_to_repo: new_project.ssh_url_to_repo
} Ci::CreateProjectService.new.execute(
current_user,
HTTParty.post( params,
fork_registration_path, ci_project
body: {
project_id: project.id,
project_token: token,
private_token: private_token,
data: params },
verify: false
) )
end end
def commit_coverage(sha, ref) def commit_coverage(sha, ref)
response = get_ci_build(sha, ref) get_ci_commit(sha, ref).coverage
rescue ActiveRecord::RecordNotFound
if response.code == 200 and response["coverage"] :error
response["coverage"]
end
rescue Errno::ECONNREFUSED
nil
end end
def build_page(sha, ref) def build_page(sha, ref)
URI::encode(project_url + "/refs/#{ref}/commits/#{sha}") if project.gitlab_ci_project.present?
end ci_project_ref_commits_url(project.gitlab_ci_project, ref, sha)
end
def builds_path
project_url + "?ref=" + project.default_branch
end
def status_img_path
project_url + "/status.png?ref=" + project.default_branch
end end
def title def title
...@@ -135,11 +107,7 @@ class GitlabCiService < CiService ...@@ -135,11 +107,7 @@ class GitlabCiService < CiService
end end
def fields def fields
[ []
{ type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
{ type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' },
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
]
end end
private private
...@@ -148,10 +116,6 @@ class GitlabCiService < CiService ...@@ -148,10 +116,6 @@ class GitlabCiService < CiService
repository.blob_at(sha, '.gitlab-ci.yml') repository.blob_at(sha, '.gitlab-ci.yml')
end end
def fork_registration_path
project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks")
end
def repository def repository
project.repository project.repository
end end
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
# noteable_type :string(255) # noteable_type :string(255)
# recipient_id :integer # recipient_id :integer
# commit_id :string(255) # commit_id :string(255)
# line_code :string(255)
# reply_key :string(255) not null # reply_key :string(255) not null
# #
...@@ -21,13 +22,20 @@ class SentNotification < ActiveRecord::Base ...@@ -21,13 +22,20 @@ class SentNotification < ActiveRecord::Base
validates :noteable_id, presence: true, unless: :for_commit? validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit? validates :commit_id, presence: true, if: :for_commit?
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
class << self class << self
def reply_key
return nil unless Gitlab::IncomingEmail.enabled?
SecureRandom.hex(16)
end
def for(reply_key) def for(reply_key)
find_by(reply_key: reply_key) find_by(reply_key: reply_key)
end end
def record(noteable, recipient_id, reply_key) def record(noteable, recipient_id, reply_key, params = {})
return unless reply_key return unless reply_key
noteable_id = nil noteable_id = nil
...@@ -38,7 +46,7 @@ class SentNotification < ActiveRecord::Base ...@@ -38,7 +46,7 @@ class SentNotification < ActiveRecord::Base
noteable_id = noteable.id noteable_id = noteable.id
end end
create( params.reverse_merge!(
project: noteable.project, project: noteable.project,
noteable_type: noteable.class.name, noteable_type: noteable.class.name,
noteable_id: noteable_id, noteable_id: noteable_id,
...@@ -46,6 +54,14 @@ class SentNotification < ActiveRecord::Base ...@@ -46,6 +54,14 @@ class SentNotification < ActiveRecord::Base
recipient_id: recipient_id, recipient_id: recipient_id,
reply_key: reply_key reply_key: reply_key
) )
create(params)
end
def record_note(note, recipient_id, reply_key, params = {})
params[:line_code] = note.line_code
record(note.noteable, recipient_id, reply_key, params)
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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