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.
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 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)
......@@ -42,6 +89,9 @@ v 8.0.0 (unreleased)
- Retrieving oauth token with LDAP credentials
- Load Application settings from running database unless env var USE_DB=false
- 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)
- Added web_url key project hook_attrs (Kirill Zaitsev)
- Add ability to get user information by ID of an SSH key via the API
......@@ -49,6 +99,7 @@ v 8.0.0 (unreleased)
- Add support for Crowd
- Global Labels that are available to all projects
- 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)
- Add FogBugz project import (Jared Szechy)
- Sort users autocomplete lists by user (Allister Antosik)
......@@ -56,6 +107,8 @@ v 8.0.0 (unreleased)
- Add ability to add custom text to the help page (Jeroen van Baarsen)
- Add pg_schema to backup config
- 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
- No changes
......
This diff is collapsed.
......@@ -22,7 +22,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem "devise", '~> 3.2.4'
gem "devise", '~> 3.5.2'
gem "devise-async", '~> 0.9.0'
gem 'omniauth', "~> 1.2.2"
gem 'omniauth-google-oauth2', '~> 0.2.5'
......@@ -38,7 +38,7 @@ gem 'omniauth_crowd'
gem "rack-oauth2", "~> 1.0.5"
# Two-factor authentication
gem 'devise-two-factor', '~> 1.0.1'
gem 'devise-two-factor', '~> 2.0.0'
gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 1.3.4'
......@@ -77,7 +77,7 @@ gem "stamp", '~> 0.5.0'
gem 'enumerize', '~> 0.7.0'
# Pagination
gem "kaminari", "~> 0.15.1"
gem "kaminari", "~> 0.16.3"
# HAML
gem "haml-rails", '~> 0.5.3'
......@@ -121,6 +121,8 @@ end
# State machine
gem "state_machine", '~> 1.2.0'
# Run events after state machine commits
gem 'after_commit_queue'
# Issue tags
gem 'acts-as-taggable-on', '~> 3.4'
......@@ -283,10 +285,11 @@ group :production do
end
gem "newrelic_rpm", '~> 3.9.4.245'
gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.5.1"
gem "mail_room", "~> 0.5.2"
gem 'email_reply_parser', '~> 0.5.8'
......
......@@ -42,6 +42,8 @@ GEM
acts-as-taggable-on (3.5.0)
activerecord (>= 3.2, < 5)
addressable (2.3.8)
after_commit_queue (1.1.0)
rails (>= 3.0)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
rake (~> 10.4)
......@@ -136,21 +138,21 @@ GEM
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (3.2.4)
devise (3.5.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
responders
thread_safe (~> 0.1)
warden (~> 1.2.3)
devise-async (0.9.0)
devise (~> 3.2)
devise-two-factor (1.0.2)
activemodel
devise-two-factor (2.0.0)
activesupport
attr_encrypted (~> 1.3.2)
devise (>= 3.2.4, < 3.5)
devise (~> 3.5.0)
railties
rotp (< 2)
rotp (~> 2)
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
......@@ -367,7 +369,7 @@ GEM
railties (>= 3.2.16)
json (1.8.3)
jwt (1.5.1)
kaminari (0.15.1)
kaminari (0.16.3)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.9.3)
......@@ -384,7 +386,7 @@ GEM
systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mail_room (0.5.1)
mail_room (0.5.2)
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
......@@ -402,6 +404,9 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (2.9.2)
netrc (0.10.3)
newrelic-grape (2.0.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
......@@ -558,12 +563,14 @@ GEM
request_store (1.2.0)
rerun (0.10.0)
listen (~> 2.7, >= 2.7.3)
responders (1.1.2)
railties (>= 3.2, < 4.2)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rinku (1.7.3)
rotp (1.6.1)
rotp (2.1.1)
rouge (1.10.1)
rqrcode (0.7.0)
chunky_png
......@@ -782,6 +789,7 @@ DEPENDENCIES
activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
annotate (~> 2.6.0)
asana (~> 0.0.6)
asciidoctor (~> 1.5.2)
......@@ -806,9 +814,9 @@ DEPENDENCIES
d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
devise (~> 3.2.4)
devise (~> 3.5.2)
devise-async (~> 0.9.0)
devise-two-factor (~> 1.0.1)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
doorkeeper (~> 2.1.3)
dropzonejs-rails (~> 0.7.1)
......@@ -844,13 +852,14 @@ DEPENDENCIES
jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.0.1)
jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.15.1)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
mail_room (~> 0.5.1)
mail_room (~> 0.5.2)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
nested_form (~> 0.3.2)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
nprogress-rails (~> 0.1.2.3)
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
$(this).submit()
$('.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:
top: ->
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@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
type: 'GET'
url: $('.merge-request').data('url')
success: (data) =>
switch data.state
when 'merged'
location.reload()
else
setTimeout(merge_request_widget.mergeInProgress, 2000)
if data.state == "merged"
location.reload()
else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else
setTimeout(merge_request_widget.mergeInProgress, 2000)
dataType: 'json'
getMergeStatus: ->
......
......@@ -24,3 +24,19 @@ class @Project
$.cookie('hide_no_password_message', 'false', { path: path })
$(@).parents('.no-password-message').remove()
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;
//
//##
$pagination-color: #fff;
$pagination-bg: $brand-success;
$pagination-color: $gl-gray;
$pagination-bg: $background-color;
$pagination-border: transparent;
$pagination-hover-color: #fff;
$pagination-hover-bg: darken($brand-success, 15%);
$pagination-hover-bg: $brand-info;
$pagination-hover-border: transparent;
$pagination-active-color: #fff;
$pagination-active-bg: darken($brand-success, 15%);
$pagination-active-bg: $brand-info;
$pagination-active-border: transparent;
$pagination-disabled-color: #b4bcc2;
$pagination-disabled-bg: lighten($brand-success, 15%);
$pagination-disabled-color: #fff;
$pagination-disabled-bg: lighten($brand-info, 15%);
$pagination-disabled-border: transparent;
......
......@@ -93,46 +93,88 @@
}
h1 {
margin-top: 45px;
font-size: 2.5em;
font-size: 1.3em;
font-weight: 600;
margin: 24px 0 12px 0;
padding: 0 0 10px 0;
border-bottom: 1px solid #e7e9ed;
color: #313236;
}
h2 {
margin-top: 40px;
font-size: 2em;
font-size: 1.2em;
font-weight: 600;
margin: 24px 0 12px 0;
color: #313236;
}
h3 {
margin-top: 35px;
font-size: 1.5em;
margin: 24px 0 12px 0;
font-size: 1.25em;
}
h4 {
margin-top: 30px;
font-size: 1.2em;
margin: 24px 0 12px 0;
font-size: 1.1em;
}
h5 {
margin: 24px 0 12px 0;
font-size: 1em;
}
h6 {
margin: 24px 0 12px 0;
font-size: 0.90em;
}
blockquote {
color: #888;
padding: 8px 21px;
margin: 12px 0 12px;
border-left: 3px solid #e7e9ed;
}
blockquote p {
color: #7f8fa4 !important;
font-size: 15px;
line-height: 1.5;
}
p {
color:#5c5d5e;
margin:6px 0 0 0;
}
table {
@extend .table;
@extend .table-bordered;
margin: 12px 0 12px 0;
color: #5c5d5e;
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 {
font-size: inherit;
font-weight: inherit;
}
ul {
color: #5c5d5e;
}
li {
line-height: 1.5;
line-height: 1.6em;
}
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
......@@ -152,6 +194,7 @@
}
}
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
......@@ -183,7 +226,7 @@
&.active {
background: #f9f9f9;
a {
font-weight: bold;
font-weight: 600;
}
}
......@@ -251,3 +294,8 @@
}
}
}
.fa-align {
top: 20px;
position: relative;
}
......@@ -12,8 +12,8 @@ $sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
$border-color: #E7E9ED;
$background-color: #F8FAFC;
$border-color: #dce0e6;
$background-color: #F7F8FA;
$header-height: 58px;
$fixed-layout-width: 1200px;
$gl-gray: #7f8fa4;
......
......@@ -13,31 +13,6 @@
.builds,
.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 {
border-color: $border-color;
}
......@@ -47,8 +22,8 @@
}
td {
color: $gl-gray;
vertical-align: middle !important;
border-color: inherit !important;
a {
font-weight: normal;
......@@ -58,23 +33,16 @@
}
.commit-info {
font-size: 14px;
.attr-name {
font-weight: 300;
color: #666;
margin-right: 5px;
}
pre.commit-message {
font-size: 14px;
background: none;
padding: 0;
margin: 0;
border: none;
margin: 20px 0;
border-bottom: 1px solid #EEE;
padding-bottom: 20px;
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 @@
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
......@@ -42,6 +43,7 @@
&.s32 { font-size: 22px; line-height: 32px; }
&.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
&.s140 { font-size: 72px; line-height: 140px; }
&.s160 { font-size: 96px; line-height: 160px; }
}
......@@ -20,11 +20,11 @@
.gray-content-block {
margin: -$gl-padding;
background-color: #f8fafc;
background-color: $background-color;
padding: $gl-padding;
margin-bottom: 0px;
border-top: 1px solid #e7e9ed;
border-bottom: 1px solid #e7e9ed;
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
color: $gl-gray;
&.top-block {
......@@ -48,6 +48,7 @@
&.footer-block {
margin-top: 0;
border-bottom: none;
margin-bottom: -$gl-padding;
}
......
body {
text-rendering: geometricPrecision;
}
.btn {
@extend .btn-default;
......@@ -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 {
}
.btn-sign-in {
margin-top: 15px;
margin-top: 8px;
text-shadow: none;
}
......@@ -313,7 +313,7 @@ table {
}
.wiki .highlight, .note-body .highlight {
margin-bottom: 9px;
margin: 12px 0 12px 0;
}
.wiki .code {
......
......@@ -26,7 +26,6 @@ header {
min-height: $header-height;
background-color: #fff;
border: none;
border-bottom: 1px solid #EEE;
.container-fluid {
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 @@
min-height: 100vh;
width: 100%;
padding: 20px;
background: #f1f4f8;
background: #EAEBEC;
.container-fluid {
background: #FFF;
padding: $gl-padding;
border: 1px solid #e7e9ed;
min-height: 90vh;
&.container-blank {
......
......@@ -2,11 +2,24 @@
* Headers
*
*/
body {
text-rendering:optimizeLegibility;
-webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px;
}
.page-title {
margin-top: 0px;
line-height: 1.5;
font-weight: normal;
margin-bottom: 5px;
line-height: 1.3;
font-size: 1.25em;
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 {
......@@ -55,6 +68,7 @@ a > code {
@include md-typography;
word-wrap: break-word;
padding: 7px;
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
......@@ -83,12 +97,19 @@ a > code {
}
}
ul {
ul,ol {
padding: 0;
margin: 0 0 9px 25px !important;
margin: 6px 0 6px 18px !important;
}
ol {
color: #5c5d5e;
}
}
.md-area {
@include md-typography;
}
.md {
@include md-typography;
}
......@@ -101,6 +122,9 @@ textarea.js-gfm-input {
font-family: $monospace_font;
}
.md-preview {
}
.strikethrough {
text-decoration: line-through;
}
}
\ No newline at end of file
/* https://github.com/aahan/pygments-github-style */
pre.code.highlight.white,
.code.white {
background-color: #fff;
color: #333;
background-color: #f8fafc;
font-size: 13px;
color: #5b6169;
line-height: 1.6em;
.line-numbers,
.line-numbers a {
......
......@@ -10,3 +10,9 @@
.milestone-row {
@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 @@
.note-edit-form {
display: none;
font-size: 13px;
font-size: 15px;
.form-actions {
padding-left: 20px;
......
.alert_holder {
margin: -16px;
.alert-link {
font-weight: normal;
}
}
.no-ssh-key-message {
background-color: #f28d35;
margin-bottom: 16px;
}
.new_project,
.edit_project {
fieldset.features {
......@@ -19,44 +30,47 @@
background: #f7f8fa;
margin: -$gl-padding;
padding: $gl-padding;
padding-top: 40px;
padding: 44px 0 17px 0;
.project-identicon-holder {
margin-bottom: 15px;
margin-bottom: 16px;
.avatar, .identicon {
margin: 0 auto;
float: none;
}
.identicon {
@include border-radius(50%);
}
}
.project-home-dropdown {
margin: 11px 3px 0;
}
.project-home-desc {
h1 {
color: #313236;
margin: 0;
margin-bottom: 10px;
margin-bottom: 6px;
font-size: 23px;
font-weight: normal;
}
p {
color: #7f8fa4;
color: #5c5d5e;
}
}
.git-clone-holder {
max-width: 600px;
margin: 20px auto;
max-width: 498px;
.form-control {
background: #FFF;
font-size: 14px;
height: 42px;
margin-left: -1px;
}
}
......@@ -66,30 +80,37 @@
color: inherit;
}
}
.input-group {
display: inline-table;
position: relative;
top: 17px;
margin-bottom: 44px;
}
.project-repo-buttons {
margin-top: $gl-padding;
margin-bottom: 25px;
margin-top: 12px;
margin-bottom: 0px;
.btn {
@extend .btn-info;
text-transform: uppercase;
font-size: 15px;
line-height: 20px;
padding: 8px 14px;
border-radius: 3px;
margin-left: 10px;
@include bnt-project;
@include btn-info;
.count {
padding-left: 7px;
display: inline-block;
margin-left: 7px;
}
}
}
}
.split-one {
display: inline-table;
margin-right: 12px;
a {
margin: -1px !important;
}
}
.git-clone-holder {
.project-home-dropdown + & {
margin-right: 45px;
......@@ -99,23 +120,132 @@
cursor: auto;
@extend .monospace;
background: #FAFAFA;
width: 100%;
width: 101%;
}
.input-group-addon {
background: #FAFAFA;
background: #f7f8fa;
&.git-protocols {
padding: 0;
border: none;
.input-group-btn:last-child > .btn {
@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 {
.radio {
margin-bottom: 10px;
......@@ -232,15 +362,28 @@ table.table.protected-branches-list tr.no-border {
.project-stats {
text-align: center;
margin-top: 0;
margin-top: 15px;
margin-bottom: 0;
padding-top: 5px;
padding-bottom: 0;
padding-top: 10px;
padding-bottom: 4px;
ul.nav-pills {
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 {
display:inline;
}
......@@ -251,11 +394,11 @@ table.table.protected-branches-list tr.no-border {
}
li.missing a {
color: #bbb;
border: 1px dashed #ccc;
color: #5a6069;
border: 1px dashed #dce0e5;
&:hover {
background-color: #FAFAFA;
background-color: #f0f2f5;
}
}
}
......@@ -273,9 +416,37 @@ pre.light-well {
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 {
@include basic-list;
......@@ -297,9 +468,15 @@ pre.light-well {
color: #4c4e54;
}
.pull-right.light {
.project-controls {
float: right;
color: $gl-gray;
line-height: 45px;
color: #7f8fa4;
a:hover {
text-decoration: none;
}
}
.project-description {
......@@ -329,3 +506,8 @@ pre.light-well {
margin-top: -1px;
}
}
.inline-form {
display: inline-block;
}
......@@ -56,13 +56,19 @@ class Admin::UsersController < Admin::ApplicationController
end
def confirm
if user.confirm!
if user.confirm
redirect_to :back, notice: "Successfully confirmed"
else
redirect_to :back, alert: "Error occurred. User was not confirmed"
end
end
def login_as
sign_in(user)
flash[:alert] = "Logged in as #{user.username}"
redirect_to root_path
end
def disable_two_factor
user.disable_two_factor!
redirect_to admin_user_path(user),
......
......@@ -117,9 +117,14 @@ class ApplicationController < ActionController::Base
redirect_to request.original_url.gsub(/\.git\Z/, '') and return
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.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
end
@project
elsif current_user.nil?
@project = nil
......
......@@ -12,7 +12,10 @@ module Ci
def show
@builds = @runner.builds.order('id DESC').first(30)
@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.page(params[:page]).per(30)
end
......
......@@ -38,7 +38,7 @@ module Ci
end
def authorize_manage_builds!
unless can?(current_user, :admin_project, gl_project)
unless can?(current_user, :manage_builds, gl_project)
return page_404
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
class ProjectsController < Ci::ApplicationController
PROJECTS_BATCH = 100
before_action :authenticate_user!, except: [:build, :badge, :index, :show]
before_action :authenticate_user!, except: [:build, :badge, :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 :authorize_access_project!, except: [:build, :badge, :index, :show, :new, :create, :disabled]
before_action :authorize_manage_project!, only: [:edit, :integration, :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, :show, :new, :disabled]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :authenticate_token!, only: [:build]
before_action :no_cache, only: [:badge]
skip_before_action :check_enable_flag!, only: [:disabled]
......@@ -17,26 +15,6 @@ module Ci
def disabled
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
@ref = params[:ref]
......@@ -45,57 +23,6 @@ module Ci
@commits = @commits.page(params[:page]).per(20)
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
# Image with build status for sha or ref
def badge
......@@ -106,7 +33,8 @@ module Ci
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to :back
redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
end
def dumped_yaml
......@@ -124,12 +52,5 @@ module Ci
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
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
......@@ -11,10 +11,12 @@ module Ci
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(@project.gl_project)
if @runner.assign_to(project, current_user)
redirect_to ci_project_runners_path(project)
redirect_to path
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
......@@ -22,7 +24,7 @@ module Ci
runner_project = project.runner_projects.find(params[:id])
runner_project.destroy
redirect_to ci_project_runners_path(project)
redirect_to runners_path(@project.gl_project)
end
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
layout 'help'
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
def show
......
......@@ -16,27 +16,6 @@ class PasswordsController < Devise::PasswordsController
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
super
reset_password_token = Devise.token_generator.digest(
......
......@@ -9,7 +9,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
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
@codes = current_user.generate_otp_backup_codes!
current_user.save!
......
......@@ -25,4 +25,14 @@ class Projects::ApplicationController < ApplicationController
)
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
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
commit_id: @commit.id
}
@ci_commit = project.ci_commit(commit.sha)
respond_to do |format|
format.html
format.diff { render text: @commit.to_diff }
......
......@@ -16,10 +16,12 @@ class Projects::CompareController < Projects::ApplicationController
compare_result = CompareService.new.
execute(@project, head_ref, @project, base_ref)
@commits = compare_result.commits
@diffs = compare_result.diffs
@commit = @commits.last
@line_notes = []
if compare_result
@commits = compare_result.commits
@diffs = compare_result.diffs
@commit = @commits.last
@line_notes = []
end
end
def create
......
......@@ -5,6 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
before_action :ci_enabled, only: :ci
def show
respond_to do |format|
......@@ -23,6 +24,16 @@ class Projects::GraphsController < Projects::ApplicationController
@commits_per_month = @commits_graph.commits_per_month
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
def fetch_graph
......
......@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
before_action :validates_merge_request, 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
before_action :authorize_read_merge_request!
......@@ -149,6 +150,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.mergeable?
@merge_request.update(merge_error: nil)
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true
else
......@@ -257,8 +259,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits
@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?
@merge_request.unlock_mr
@merge_request.close
......@@ -277,4 +278,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:state_event, :description, :task_num, label_ids: []
)
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
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
if @project.empty_repo?
render 'projects/empty'
else
if current_user
@membership = @project.project_member_by_id(current_user.id)
end
render :show
end
else
......
......@@ -22,6 +22,10 @@ class RootController < Dashboard::ProjectsController
when 'stars'
flash.keep
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
return
end
......
......@@ -99,7 +99,7 @@ class SessionsController < Devise::SessionsController
end
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])
end
......
module AuthHelper
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?
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 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)
ci_project_ref_commits_path(commit.project, commit.ref, commit.sha)
end
......
......@@ -27,9 +27,9 @@ module Ci
commits = project.commits
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
"#{@project.gitlab_url}/new/master"
"#{project.gitlab_url}/new/master"
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
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
user = commit.send(options[:source])
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
......
......@@ -167,4 +167,23 @@ module DiffHelper
content_tag(:span, commit_id, class: 'monospace'),
].join(' ').html_safe
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
......@@ -45,7 +45,7 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
context.merge!(
context.reverse_merge!(
current_user: current_user,
path: @path,
project: @project,
......@@ -59,7 +59,7 @@ module GitlabMarkdownHelper
# TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered
def gfm(text, options = {})
options.merge!(
options.reverse_merge!(
current_user: current_user,
path: @path,
project: @project,
......@@ -165,7 +165,7 @@ module GitlabMarkdownHelper
# and return true. Otherwise return false.
def truncate_if_block(node, 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
else
truncated
......
......@@ -33,6 +33,14 @@ module GitlabRoutingHelper
edit_namespace_project_path(project.namespace, project, *args)
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)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
......
module GraphHelper
def get_refs(repo, commit)
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
refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
......
......@@ -71,4 +71,17 @@ module MergeRequestsHelper
merge_request.source_branch
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
......@@ -12,4 +12,49 @@ module NotificationsHelper
icon('circle-o', class: 'ns-default')
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
......@@ -3,7 +3,9 @@ module PreferencesHelper
# Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = {
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
# Returns an Array usable by a select field for more user-friendly option text
......
......@@ -156,8 +156,8 @@ module ProjectsHelper
end
end
def repository_size(project = nil)
"#{(project || @project).repository_size} MB"
def repository_size(project = @project)
"#{project.repository_size} MB"
rescue
# In order to prevent 500 error
# 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
def version_status_badge
if Rails.env.production?
if Rails.env.production? && current_application_settings.version_check_enabled
image_tag VersionCheck.new.url
end
end
......
......@@ -2,7 +2,6 @@ module Ci
class Notify < ActionMailer::Base
include Ci::Emails::Builds
add_template_helper Ci::ApplicationHelper
add_template_helper Ci::GitlabHelper
default_url_options[:host] = Gitlab.config.gitlab.host
......
......@@ -12,7 +12,7 @@ module Emails
to: recipient(recipient_id),
subject: subject("#{@commit.title} (#{@commit.short_id})"))
SentNotification.record(@commit, recipient_id, reply_key)
SentNotification.record_note(@note, recipient_id, reply_key)
end
def note_issue_email(recipient_id, note_id)
......@@ -27,7 +27,7 @@ module Emails
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
SentNotification.record_note(@note, recipient_id, reply_key)
end
def note_merge_request_email(recipient_id, note_id)
......@@ -43,7 +43,7 @@ module Emails
to: recipient(recipient_id),
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
......@@ -50,10 +50,11 @@ module Emails
subject: subject("Invitation declined"))
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
@project = Project.find project_id
@target_url = namespace_project_url(@project.namespace, @project)
@old_path_with_namespace = old_path_with_namespace
mail(to: @user.notification_email,
subject: subject("Project was moved"))
end
......
......@@ -110,7 +110,7 @@ class Notify < BaseMailer
if 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
headers['Reply-To'] = address
......@@ -150,6 +150,6 @@ class Notify < BaseMailer
end
def reply_key
@reply_key ||= Gitlab::ReplyByEmail.reply_key
@reply_key ||= SentNotification.reply_key
end
end
......@@ -30,7 +30,6 @@ module Ci
LAZY_ATTRIBUTES = ['trace']
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :project, class_name: 'Ci::Project'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
......@@ -80,7 +79,6 @@ module Ci
new_build.commands = build.commands
new_build.tag_list = build.tag_list
new_build.commit_id = build.commit_id
new_build.project_id = build.project_id
new_build.name = build.name
new_build.allow_failure = build.allow_failure
new_build.stage = build.stage
......@@ -137,7 +135,7 @@ module Ci
state :canceled, value: 'canceled'
end
delegate :sha, :short_sha, :before_sha, :ref,
delegate :sha, :short_sha, :before_sha, :ref, :project,
to: :commit, prefix: false
def trace_html
......@@ -188,7 +186,7 @@ module Ci
end
def project_id
commit.project_id
commit.project.id
end
def project_name
......
......@@ -18,8 +18,8 @@
module Ci
class Commit < ActiveRecord::Base
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 :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
......@@ -36,6 +36,14 @@ module Ci
sha
end
def project
@project ||= gl_project.ensure_gitlab_ci_project
end
def project_id
project.id
end
def last_build
builds.order(:id).last
end
......@@ -111,15 +119,14 @@ module Ci
builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs|
builds.create!({
project: project,
name: build_attrs[:name],
commands: build_attrs[:script],
tag_list: build_attrs[:tags],
options: build_attrs[:options],
allow_failure: build_attrs[:allow_failure],
stage: build_attrs[:stage],
trigger_request: trigger_request,
})
name: build_attrs[:name],
commands: build_attrs[:script],
tag_list: build_attrs[:tags],
options: build_attrs[:options],
allow_failure: build_attrs[:allow_failure],
stage: build_attrs[:stage],
trigger_request: trigger_request,
})
end
end
......@@ -236,7 +243,7 @@ module Ci
end
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
save_yaml_error(e.message)
nil
......
......@@ -33,8 +33,6 @@ module Ci
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 :runners, through: :runner_projects, class_name: 'Ci::Runner'
has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
......@@ -50,19 +48,18 @@ module Ci
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
#
validates_presence_of :name, :timeout, :token, :default_ref,
:path, :ssh_url_to_repo, :gitlab_id
validates_presence_of :timeout, :token, :default_ref, :gitlab_id
validates_uniqueness_of :gitlab_id
validates :polling_interval,
presence: true,
if: ->(project) { project.always_build.present? }
scope :public_only, ->() { where(public: true) }
presence: true,
if: ->(project) { project.always_build.present? }
before_validation :set_default_values
......@@ -78,11 +75,8 @@ module Ci
def parse(project)
params = {
name: project.name_with_namespace,
gitlab_id: project.id,
path: project.path_with_namespace,
default_ref: project.default_branch || 'master',
ssh_url_to_repo: project.ssh_url_to_repo,
email_add_pusher: current_application_settings.add_pusher,
email_only_broken_builds: current_application_settings.all_broken_builds,
}
......@@ -92,21 +86,6 @@ module Ci
project
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)
where(gitlab_id: project.id).any?
end
......@@ -114,19 +93,26 @@ module Ci
def unassigned(runner)
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}").
where('#{Ci::RunnerProject.table_name}.project_id' => nil)
where("#{Ci::RunnerProject.table_name}.project_id" => nil)
end
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)"
joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.id = last_commit.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}.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")
end
end
def search(query)
where("LOWER(#{Ci::Project.table_name}.name) LIKE :query",
query: "%#{query.try(:downcase)}%")
end
def name
name_with_namespace
end
def path
path_with_namespace
end
def gitlab_url
web_url
end
def any_runners?
......@@ -139,10 +125,11 @@ module Ci
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
self.default_ref ||= 'master'
end
def tracked_refs
@tracked_refs ||= default_ref.split(",").map{|ref| ref.strip}
@tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
end
def valid_token? token
......@@ -182,7 +169,7 @@ module Ci
# using http and basic auth
def repo_url_with_auth
auth = "gitlab-ci-token:#{token}@"
url = gitlab_url + ".git"
url = http_url_to_repo + ".git"
url.sub(/^https?:\/\//) do |prefix|
prefix + auth
end
......@@ -214,12 +201,16 @@ module Ci
end
end
def gitlab_url
File.join(Gitlab.config.gitlab.url, path)
end
def setup_finished?
commits.any?
end
def commits
gl_project.ci_commits
end
def builds
gl_project.ci_builds
end
end
end
......@@ -41,6 +41,10 @@ module Ci
query: "%#{query.try(:downcase)}%")
end
def gl_projects_ids
projects.select(:gitlab_id)
end
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
......
......@@ -433,10 +433,22 @@ class MergeRequest < ActiveRecord::Base
target_project.repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{source_branch}",
"refs/merge-requests/#{iid}/head"
ref_path
)
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
begin
lock_mr
......
......@@ -123,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base
if new_diffs.any?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
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
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
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
......
......@@ -137,7 +137,9 @@ class Namespace < ActiveRecord::Base
end
def send_update_instructions
projects.each(&:send_move_instructions)
projects.each do |project|
project.send_move_instructions("#{path_was}/#{project.path}")
end
end
def kind
......
......@@ -12,7 +12,7 @@ class Notification
class << self
def notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION]
[N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH]
end
def options_with_labels
......@@ -26,7 +26,7 @@ class Notification
end
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
......@@ -57,4 +57,21 @@ class Notification
def level
target.notification_level
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
......@@ -39,6 +39,7 @@ class Project < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Referable
include Sortable
include AfterCommitQueue
extend Gitlab::ConfigHelper
extend Enumerize
......@@ -117,6 +118,8 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
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 :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
......@@ -191,7 +194,7 @@ class Project < ActiveRecord::Base
state :finished
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
end
......@@ -235,10 +238,10 @@ class Project < ActiveRecord::Base
return nil unless id.include?('/')
id = id.split('/')
namespace = Namespace.find_by(path: id.first)
namespace = Namespace.by_path(id.first)
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
def visibility_levels
......@@ -275,13 +278,17 @@ class Project < ActiveRecord::Base
id && persisted?
end
def schedule_add_import_job
run_after_commit(:add_import_job)
end
def add_import_job
if forked?
unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path)
import_fail
end
else
RepositoryImportWorker.perform_in(2.seconds, id)
RepositoryImportWorker.perform_async(id)
end
end
......@@ -428,7 +435,7 @@ class Project < ActiveRecord::Base
end
def gitlab_ci?
gitlab_ci_service && gitlab_ci_service.active
gitlab_ci_service && gitlab_ci_service.active && gitlab_ci_project.present?
end
def ci_services
......@@ -474,8 +481,8 @@ class Project < ActiveRecord::Base
end
end
def send_move_instructions
NotificationService.new.project_was_moved(self)
def send_move_instructions(old_path_with_namespace)
NotificationService.new.project_was_moved(self, old_path_with_namespace)
end
def owner
......@@ -617,7 +624,7 @@ class Project < ActiveRecord::Base
# So we basically we mute exceptions in next actions
begin
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
rescue
# Returning false does not rollback after_* transaction but gives
......@@ -735,4 +742,22 @@ class Project < ActiveRecord::Base
errors.add(:base, 'Failed create wiki')
false
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
......@@ -69,14 +69,6 @@ class BuildkiteService < CiService
"#{project_url}/builds?commit=#{sha}"
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
'Buildkite'
end
......
module Ci
class HipChatMessage
include Gitlab::Application.routes.url_helpers
attr_reader :build
def initialize(build)
......@@ -8,13 +10,13 @@ module Ci
def to_s
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?
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
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
lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
......
......@@ -2,6 +2,8 @@ require 'slack-notifier'
module Ci
class SlackMessage
include Gitlab::Application.routes.url_helpers
def initialize(commit)
@commit = commit
end
......@@ -27,7 +29,7 @@ module Ci
next unless build.failed?
fields << {
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
......@@ -44,12 +46,12 @@ module Ci
attr_reader :commit
def attachment_message
out = "<#{Ci::RoutesHelper.ci_project_url(project)}|#{project_name}>: "
out = "<#{ci_project_url(project)}|#{project_name}>: "
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
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
out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
out << "of <#{commit_ref_link}|#{commit.ref}> "
......
......@@ -26,7 +26,7 @@ class DroneCiService < CiService
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?
if: :activated?
after_save :compose_service_hook, if: :activated?
......@@ -135,20 +135,6 @@ class DroneCiService < CiService
commit_page(sha, ref)
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
'Drone CI'
end
......
......@@ -19,22 +19,12 @@
#
class GitlabCiService < CiService
API_PREFIX = "api/v1"
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?
include Gitlab::Application.routes.url_helpers
after_save :compose_service_hook, if: :activated?
def compose_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
end
......@@ -55,71 +45,53 @@ class GitlabCiService < CiService
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
def commit_status_path(sha, ref)
URI::encode(project_url + "/refs/#{ref}/commits/#{sha}/status.json?token=#{token}")
def token
if project.gitlab_ci_project.present?
project.gitlab_ci_project.token
end
end
def get_ci_build(sha, ref)
@ci_builds ||= {}
@ci_builds[sha] ||= HTTParty.get(commit_status_path(sha, ref), verify: false)
def get_ci_commit(sha, ref)
Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha_and_ref!(sha, ref)
end
def commit_status(sha, ref)
response = get_ci_build(sha, ref)
if response.code == 200 and response["status"]
response["status"]
else
:error
end
rescue Errno::ECONNREFUSED
get_ci_commit(sha, ref).status
rescue ActiveRecord::RecordNotFound
:error
end
def fork_registration(new_project, private_token)
params = {
def fork_registration(new_project, current_user)
params = OpenStruct.new({
id: new_project.id,
name_with_namespace: new_project.name_with_namespace,
path_with_namespace: new_project.path_with_namespace,
web_url: new_project.web_url,
default_branch: new_project.default_branch,
ssh_url_to_repo: new_project.ssh_url_to_repo
}
HTTParty.post(
fork_registration_path,
body: {
project_id: project.id,
project_token: token,
private_token: private_token,
data: params },
verify: false
default_branch: new_project.default_branch
})
ci_project = Ci::Project.find_by!(gitlab_id: project.id)
Ci::CreateProjectService.new.execute(
current_user,
params,
ci_project
)
end
def commit_coverage(sha, ref)
response = get_ci_build(sha, ref)
if response.code == 200 and response["coverage"]
response["coverage"]
end
rescue Errno::ECONNREFUSED
nil
get_ci_commit(sha, ref).coverage
rescue ActiveRecord::RecordNotFound
:error
end
def build_page(sha, ref)
URI::encode(project_url + "/refs/#{ref}/commits/#{sha}")
end
def builds_path
project_url + "?ref=" + project.default_branch
end
def status_img_path
project_url + "/status.png?ref=" + project.default_branch
if project.gitlab_ci_project.present?
ci_project_ref_commits_url(project.gitlab_ci_project, ref, sha)
end
end
def title
......@@ -135,11 +107,7 @@ class GitlabCiService < CiService
end
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
private
......@@ -148,10 +116,6 @@ class GitlabCiService < CiService
repository.blob_at(sha, '.gitlab-ci.yml')
end
def fork_registration_path
project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks")
end
def repository
project.repository
end
......
......@@ -8,6 +8,7 @@
# noteable_type :string(255)
# recipient_id :integer
# commit_id :string(255)
# line_code :string(255)
# reply_key :string(255) not null
#
......@@ -21,13 +22,20 @@ class SentNotification < ActiveRecord::Base
validates :noteable_id, presence: true, unless: :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
def reply_key
return nil unless Gitlab::IncomingEmail.enabled?
SecureRandom.hex(16)
end
def for(reply_key)
find_by(reply_key: reply_key)
end
def record(noteable, recipient_id, reply_key)
def record(noteable, recipient_id, reply_key, params = {})
return unless reply_key
noteable_id = nil
......@@ -38,7 +46,7 @@ class SentNotification < ActiveRecord::Base
noteable_id = noteable.id
end
create(
params.reverse_merge!(
project: noteable.project,
noteable_type: noteable.class.name,
noteable_id: noteable_id,
......@@ -46,6 +54,14 @@ class SentNotification < ActiveRecord::Base
recipient_id: recipient_id,
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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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