Commit f96abe4b authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into feature/slack-notifications-on-ci

# Conflicts:
#	.gitlab-ci.yml
parents c3fe7388 5803a530
image: "ruby:2.2" image: "ruby:2.1"
services: services:
- mysql:latest - mysql:latest
...@@ -6,7 +6,7 @@ services: ...@@ -6,7 +6,7 @@ services:
- redis:latest - redis:latest
cache: cache:
key: "ruby22" key: "ruby21"
paths: paths:
- vendor - vendor
...@@ -160,9 +160,9 @@ bundler:audit: ...@@ -160,9 +160,9 @@ bundler:audit:
- mysql - mysql
allow_failure: true allow_failure: true
## Ruby 2.1 jobs # Ruby 2.1 jobs
spec:feature:ruby21: spec:feature:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.1
only: only:
...@@ -171,82 +171,82 @@ spec:feature:ruby21: ...@@ -171,82 +171,82 @@ spec:feature:ruby21:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spec:api:ruby21: spec:api:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spec:models:ruby21: spec:models:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spec:lib:ruby21: spec:lib:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spec:services:ruby21: spec:services:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spec:benchmark:ruby21: spec:benchmark:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake spec:benchmark - RAILS_ENV=test bundle exec rake spec:benchmark
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
...@@ -254,60 +254,60 @@ spec:benchmark:ruby21: ...@@ -254,60 +254,60 @@ spec:benchmark:ruby21:
- mysql - mysql
allow_failure: true allow_failure: true
spec:other:ruby21: spec:other:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spinach:project:half:ruby21: spinach:project:half:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spinach:project:rest:ruby21: spinach:project:rest:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
- ruby - ruby
- mysql - mysql
spinach:other:ruby21: spinach:other:ruby22:
stage: test stage: test
image: ruby:2.1 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
cache: cache:
key: "ruby21" key: "ruby22"
paths: paths:
- vendor - vendor
tags: tags:
......
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.5.0 (unreleased) v 8.6.0 (unreleased)
v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse) - Cache various Repository methods to improve performance (Yorick Peterse)
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu) - Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
...@@ -46,23 +48,38 @@ v 8.5.0 (unreleased) ...@@ -46,23 +48,38 @@ v 8.5.0 (unreleased)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues - Prevent parse error when name of project ends with .atom and prevent path issues
- Discover branches for commit statuses ref-less when doing merge when succeeded
- Mark inline difference between old and new paths when a file is renamed - Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu) - Support Akismet spam checking for creation of issues via API (Stan Hu)
- API: Allow to set or update a merge-request's milestone (Kirill Skachkov) - API: Allow to set or update a merge-request's milestone (Kirill Skachkov)
- Improve UI consistency between projects and groups lists - Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page - Add sort dropdown to dashboard projects page
- Fixed logo animation on Safari (Roman Rott) - Fixed logo animation on Safari (Roman Rott)
- Fix Merge When Succeeded when multiple stages
- Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg) - Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg)
- In seach autocomplete show only groups and projects you are member of - In seach autocomplete show only groups and projects you are member of
- Don't process cross-reference notes from forks - Don't process cross-reference notes from forks
- Fix: init.d script not working on OS X - Fix: init.d script not working on OS X
- Faster snippet search - Faster snippet search
- Added API to download build artifacts
- Title for milestones should be unique (Zeger-Jan van de Weg) - Title for milestones should be unique (Zeger-Jan van de Weg)
- Validate correctness of maximum attachment size application setting - Validate correctness of maximum attachment size application setting
- Replaces "Create merge request" link with one to the "Merge Request" when one exists - Replaces "Create merge request" link with one to the "Merge Request" when one exists
- Fix CI builds badge, add a new link to builds badge, deprecate the old one - Fix CI builds badge, add a new link to builds badge, deprecate the old one
- Fix broken link to project in build notification emails - Fix broken link to project in build notification emails
- Ability to see and sort on vote count from Issues and MR lists - Ability to see and sort on vote count from Issues and MR lists
- Fix builds scheduler when first build in stage was allowed to fail
- User project limit is reached notice is hidden if the projects limit is zero
- Add API support for managing runners and project's runners
- Allow SAML users to login with no previous account without having to allow
all Omniauth providers to do so.
- Allow existing users to auto link their SAML credentials by logging in via SAML
- Make it possible to erase a build (trace, artifacts) using UI and API
- Ability to revert changes from a Merge Request or Commit
- Emoji comment on diffs are not award emoji
- Add label description (Nuttanart Pornprasitsakul)
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
v 8.4.4 v 8.4.4
- Update omniauth-saml gem to 1.4.2 - Update omniauth-saml gem to 1.4.2
......
...@@ -50,7 +50,7 @@ gem "browser", '~> 1.0.0' ...@@ -50,7 +50,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 8.1' gem "gitlab_git", '~> 8.2'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -204,7 +204,7 @@ gem 'jquery-turbolinks', '~> 2.1.0' ...@@ -204,7 +204,7 @@ gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8' gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0' gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.2' gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.2.0' gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1' gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.0.0' gem 'jquery-rails', '~> 4.0.0'
......
...@@ -336,11 +336,11 @@ GEM ...@@ -336,11 +336,11 @@ GEM
ruby-progressbar (~> 1.4) ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6) gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21) rugged (~> 0.21)
gemojione (2.1.1) gemojione (2.2.1)
json json
get_process_mem (0.2.0) get_process_mem (0.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
github-linguist (4.7.3) github-linguist (4.7.5)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0) escape_utils (~> 1.1.0)
mime-types (>= 1.19) mime-types (>= 1.19)
...@@ -355,13 +355,13 @@ GEM ...@@ -355,13 +355,13 @@ GEM
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (~> 1.15)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.2.0) gitlab_emoji (0.3.1)
gemojione (~> 2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (8.1.0) gitlab_git (8.2.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
rugged (~> 0.23.3) rugged (~> 0.24.0b13)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1) gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9) net-ldap (~> 0.9)
...@@ -700,7 +700,7 @@ GEM ...@@ -700,7 +700,7 @@ GEM
rubyntlm (0.5.2) rubyntlm (0.5.2)
rubypants (0.2.0) rubypants (0.2.0)
rufus-scheduler (3.1.10) rufus-scheduler (3.1.10)
rugged (0.23.3) rugged (0.24.0b13)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -931,8 +931,8 @@ DEPENDENCIES ...@@ -931,8 +931,8 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
github-markup (~> 1.3.1) github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.2.0) gitlab_emoji (~> 0.3.0)
gitlab_git (~> 8.1) gitlab_git (~> 8.2)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
......
...@@ -67,7 +67,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the ...@@ -67,7 +67,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software: GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL - Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1 or 2.2 - Ruby (MRI) 2.1
- Git 1.7.10+ - Git 1.7.10+
- Redis 2.8+ - Redis 2.8+
- MySQL or PostgreSQL - MySQL or PostgreSQL
......
8.5.0-pre 8.6.0-pre
app/assets/images/emoji.png

813 KB | W: | H:

app/assets/images/emoji.png

257 KB | W: | H:

app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
app/assets/images/emoji.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -15,3 +15,5 @@ class @IssuableContext ...@@ -15,3 +15,5 @@ class @IssuableContext
block.find('.selectbox').show() block.find('.selectbox').show()
block.find('.value').hide() block.find('.value').hide()
block.find('.js-select2').select2("open") block.find('.js-select2').select2("open")
$(".right-sidebar").niceScroll()
...@@ -146,6 +146,7 @@ class @MergeRequestTabs ...@@ -146,6 +146,7 @@ class @MergeRequestTabs
success: (data) => success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html document.querySelector("div#diffs").innerHTML = data.html
$('div#diffs .js-syntax-highlight').syntaxHighlight() $('div#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true @diffsLoaded = true
@scrollToElement("#diffs") @scrollToElement("#diffs")
...@@ -177,3 +178,10 @@ class @MergeRequestTabs ...@@ -177,3 +178,10 @@ class @MergeRequestTabs
options = $.extend({}, defaults, options) options = $.extend({}, defaults, options)
$.ajax(options) $.ajax(options)
# Returns diff view type
diffViewType: ->
$('.inline-parallel-buttons a.active').data('view-type')
expandViewContainer: ->
$('.container-fluid').removeClass('container-limited')
...@@ -64,6 +64,7 @@ class @Milestone ...@@ -64,6 +64,7 @@ class @Milestone
constructor: -> constructor: ->
@bindIssuesSorting() @bindIssuesSorting()
@bindMergeRequestSorting() @bindMergeRequestSorting()
@bindTabsSwitching
bindIssuesSorting: -> bindIssuesSorting: ->
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable( $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
...@@ -122,3 +123,12 @@ class @Milestone ...@@ -122,3 +123,12 @@ class @Milestone
Milestone.updateMergeRequest(ui.item, merge_request_url, data) Milestone.updateMergeRequest(ui.item, merge_request_url, data)
).disableSelection() ).disableSelection()
bindMergeRequestSorting: ->
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
currentTabClass = $(e.target).data('show')
previousTabClass = $(e.relatedTarget).data('show')
$(previousTabClass).hide()
$(currentTabClass).removeClass('hidden')
$(currentTabClass).show()
...@@ -118,19 +118,3 @@ body { ...@@ -118,19 +118,3 @@ body {
@include gitlab-theme(#9988CC, $theme-violet, #443366, #332255); @include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
} }
} }
::-webkit-scrollbar{
width: 3px;
}
::-webkit-scrollbar-thumb{
background-color:$theme-charcoal; border-radius: 0;
}
::-webkit-scrollbar-thumb:hover{
background-color:$theme-charcoal;
}
::-webkit-scrollbar-track{
background-color:#FFF;
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
display: inline-block; display: inline-block;
} }
.issue-no-comments, .issue-no-votes { .issue-no-comments {
opacity: 0.5; opacity: 0.5;
} }
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
} }
} }
.manage-labels-list { .label-row {
.label { .label {
padding: 9px; padding: 9px;
font-size: 14px; font-size: 14px;
......
...@@ -163,7 +163,7 @@ ...@@ -163,7 +163,7 @@
display: inline-block; display: inline-block;
} }
.merge-request-no-comments, .merge-request-no-votes { .merge-request-no-comments {
opacity: 0.5; opacity: 0.5;
} }
} }
......
...@@ -11,3 +11,60 @@ li.milestone { ...@@ -11,3 +11,60 @@ li.milestone {
height: 6px; height: 6px;
} }
} }
.milestone-content {
.issues-count {
margin-right: 17px;
float: right;
width: 105px;
}
.issue-row {
.color-label {
border-radius: 2px;
padding: 3px !important;
}
// Issue title
span a {
color: rgba(0,0,0,0.64);
}
}
}
.milestone-summary {
margin-bottom: 25px;
.milestone-stat {
margin-right: 10px;
}
.time-elapsed {
color: $orange-light;
}
}
.issues-sortable-list {
.issue-detail {
display: block;
.issue-number{
color: rgba(0,0,0,0.44);
margin-right: 5px;
}
.color-label {
padding: 6px 10px;
margin-right: 7px;
margin-top: 10px;
}
.avatar {
float: none;
}
}
}
.milestone-detail {
border-bottom: 1px solid $border-color;
padding: 20px 0;
}
...@@ -73,24 +73,19 @@ ...@@ -73,24 +73,19 @@
font-weight: normal; font-weight: normal;
} }
.visibility-icon {
display: inline-block;
margin-left: 5px;
font-size: 18px;
color: $gray;
}
p { p {
padding: 0 $gl-padding; padding: 0 $gl-padding;
color: #5c5d5e; color: #5c5d5e;
} }
} }
.visibility-level-label {
@extend .btn;
@extend .btn-gray;
color: $gray;
cursor: default;
i {
color: inherit;
}
}
.project-repo-buttons { .project-repo-buttons {
margin-top: 20px; margin-top: 20px;
margin-bottom: 0px; margin-bottom: 0px;
......
/**
* Dashboard Todos
*
*/
.navbar-nav {
li {
.badge.todos-pending-count {
background-color: #7f8fa4;
margin-top: -5px;
}
}
}
.todos {
.panel {
border-top: none;
margin-bottom: 0;
}
}
.todo-item {
font-size: $gl-font-size;
padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color;
color: #7f8fa4;
&.todo-inline {
.avatar {
position: relative;
top: -2px;
}
.todo-title {
line-height: 40px;
}
}
a {
color: #4c4e54;
}
.avatar {
margin-left: -($gl-avatar-size + $gl-padding-top);
}
.todo-title {
@include str-truncated(calc(100% - 174px));
font-weight: 600;
.author_name {
color: #333;
}
}
.todo-body {
margin-right: 174px;
.todo-note {
word-wrap: break-word;
.md {
color: #7f8fa4;
font-size: $gl-font-size;
p {
color: #5c5d5e;
}
}
pre {
border: none;
background: #f9f9f9;
border-radius: 0;
color: #777;
margin: 0 20px;
overflow: hidden;
}
.note-image-attach {
margin-top: 4px;
margin-left: 0px;
max-width: 200px;
float: none;
}
p:last-child {
margin-bottom: 0;
}
}
.todo-note-icon {
color: #777;
float: left;
font-size: $gl-font-size;
line-height: 16px;
margin-right: 5px;
}
}
&:last-child { border:none }
}
@media (max-width: $screen-xs-max) {
.todo-item {
padding-left: $gl-padding;
.todo-title {
white-space: normal;
overflow: visible;
max-width: 100%;
}
.avatar {
display: none;
}
.todo-body {
margin: 0;
border-left: 2px solid #DDD;
padding-left: 10px;
}
}
}
...@@ -53,6 +53,6 @@ class Admin::LabelsController < Admin::ApplicationController ...@@ -53,6 +53,6 @@ class Admin::LabelsController < Admin::ApplicationController
end end
def label_params def label_params
params[:label].permit(:title, :color) params[:label].permit(:title, :description, :color)
end end
end end
...@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base ...@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
helper_method :abilities, :can?, :current_application_settings helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
helper_method :repository helper_method :repository, :can_collaborate_with_project?
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -410,6 +410,13 @@ class ApplicationController < ActionController::Base ...@@ -410,6 +410,13 @@ class ApplicationController < ActionController::Base
current_user.nil? && root_path == request.path current_user.nil? && root_path == request.path
end end
def can_collaborate_with_project?(project = nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project))
end
private private
def set_default_sort def set_default_sort
......
...@@ -13,17 +13,11 @@ module CreatesCommit ...@@ -13,17 +13,11 @@ module CreatesCommit
result = service.new(@tree_edit_project, current_user, commit_params).execute result = service.new(@tree_edit_project, current_user, commit_params).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = success_notice || "Your changes have been successfully committed." update_flash_notice(success_notice)
if create_merge_request?
success_path = new_merge_request_path
target = different_project? ? "project" : "branch"
flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
end
respond_to do |format| respond_to do |format|
format.html { redirect_to success_path } format.html { redirect_to final_success_path(success_path) }
format.json { render json: { message: "success", filePath: success_path } } format.json { render json: { message: "success", filePath: final_success_path(success_path) } }
end end
else else
flash[:alert] = result[:message] flash[:alert] = result[:message]
...@@ -41,14 +35,32 @@ module CreatesCommit ...@@ -41,14 +35,32 @@ module CreatesCommit
end end
def authorize_edit_tree! def authorize_edit_tree!
return if can?(current_user, :push_code, project) return if can_collaborate_with_project?
return if current_user && current_user.already_forked?(project)
access_denied! access_denied!
end end
private private
def update_flash_notice(success_notice)
flash[:notice] = success_notice || "Your changes have been successfully committed."
if create_merge_request?
if merge_request_exists?
flash[:notice] = nil
else
target = different_project? ? "project" : "branch"
flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
end
end
end
def final_success_path(success_path)
return success_path unless create_merge_request?
merge_request_exists? ? existing_merge_request_path : new_merge_request_path
end
def new_merge_request_path def new_merge_request_path
new_namespace_project_merge_request_path( new_namespace_project_merge_request_path(
@mr_source_project.namespace, @mr_source_project.namespace,
...@@ -62,6 +74,19 @@ module CreatesCommit ...@@ -62,6 +74,19 @@ module CreatesCommit
) )
end end
def existing_merge_request_path
namespace_project_merge_request_path(@mr_target_project.namespace, @mr_target_project, @merge_request)
end
def merge_request_exists?
return @merge_request if defined?(@merge_request)
@merge_request = @mr_target_project.merge_requests.opened.find_by(
source_branch: @mr_source_branch,
target_branch: @mr_target_branch
)
end
def different_project? def different_project?
@mr_source_project != @mr_target_project @mr_source_project != @mr_target_project
end end
...@@ -75,7 +100,7 @@ module CreatesCommit ...@@ -75,7 +100,7 @@ module CreatesCommit
end end
def set_commit_variables def set_commit_variables
@mr_source_branch = @target_branch @mr_source_branch ||= @target_branch
if can?(current_user, :push_code, @project) if can?(current_user, :push_code, @project)
# Edit file in this project # Edit file in this project
...@@ -89,7 +114,7 @@ module CreatesCommit ...@@ -89,7 +114,7 @@ module CreatesCommit
else else
# Merge request to this project # Merge request to this project
@mr_target_project = @project @mr_target_project = @project
@mr_target_branch = @ref @mr_target_branch ||= @ref
end end
else else
# Edit file in fork # Edit file in fork
...@@ -97,7 +122,7 @@ module CreatesCommit ...@@ -97,7 +122,7 @@ module CreatesCommit
# Merge request from fork to this project # Merge request from fork to this project
@mr_source_project = @tree_edit_project @mr_source_project = @tree_edit_project
@mr_target_project = @project @mr_target_project = @project
@mr_target_branch = @ref @mr_target_branch ||= @ref
end end
end end
end end
...@@ -6,6 +6,8 @@ module IssuesAction ...@@ -6,6 +6,8 @@ module IssuesAction
@issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE) @issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE)
@issues = @issues.preload(:author, :project) @issues = @issues.preload(:author, :project)
@label = @issuable_finder.labels.first
respond_to do |format| respond_to do |format|
format.html format.html
format.atom { render layout: false } format.atom { render layout: false }
......
...@@ -5,5 +5,7 @@ module MergeRequestsAction ...@@ -5,5 +5,7 @@ module MergeRequestsAction
@merge_requests = get_merge_requests_collection @merge_requests = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE) @merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE)
@merge_requests = @merge_requests.preload(:author, :target_project) @merge_requests = @merge_requests.preload(:author, :target_project)
@label = @issuable_finder.labels.first
end end
end end
class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy_all]
def index
@todos = @todos.page(params[:page]).per(PER_PAGE)
end
def destroy
todo.done!
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
format.js { render nothing: true }
end
end
def destroy_all
@todos.each(&:done)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { render nothing: true }
end
end
private
def todo
@todo ||= current_user.todos.find(params[:id])
end
def find_todos
@todos = TodosFinder.new(current_user, params).execute
end
end
...@@ -42,6 +42,26 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -42,6 +42,26 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
end end
def saml
if current_user
log_audit_event(current_user, with: :saml)
# Update SAML identity if data has changed.
identity = current_user.identities.find_by(extern_uid: oauth['uid'], provider: :saml)
if identity.nil?
current_user.identities.create(extern_uid: oauth['uid'], provider: :saml)
redirect_to profile_account_path, notice: 'Authentication method updated'
else
redirect_to after_sign_in_path_for(current_user)
end
else
saml_user = Gitlab::Saml::User.new(oauth)
saml_user.save
@user = saml_user.gl_user
continue_login_process
end
end
def omniauth_error def omniauth_error
@provider = params[:provider] @provider = params[:provider]
@error = params[:error] @error = params[:error]
...@@ -65,25 +85,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -65,25 +85,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event(current_user, with: oauth['provider']) log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated' redirect_to profile_account_path, notice: 'Authentication method updated'
else else
@user = Gitlab::OAuth::User.new(oauth) oauth_user = Gitlab::OAuth::User.new(oauth)
@user.save oauth_user.save
@user = oauth_user.gl_user
# Only allow properly saved users to login.
if @user.persisted? && @user.valid?
log_audit_event(@user.gl_user, with: oauth['provider'])
sign_in_and_redirect(@user.gl_user)
else
error_message =
if @user.gl_user.errors.any?
@user.gl_user.errors.map do |attribute, message|
"#{attribute} #{message}"
end.join(", ")
else
''
end
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return continue_login_process
end
end end
rescue Gitlab::OAuth::SignupDisabledError rescue Gitlab::OAuth::SignupDisabledError
label = Gitlab::OAuth::Provider.label_for(oauth['provider']) label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
...@@ -104,6 +110,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -104,6 +110,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
session[:service_tickets][provider] = ticket session[:service_tickets][provider] = ticket
end end
def continue_login_process
# Only allow properly saved users to login.
if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider'])
sign_in_and_redirect(@user)
else
error_message = @user.errors.full_messages.to_sentence
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
def oauth def oauth
@oauth ||= request.env['omniauth.auth'] @oauth ||= request.env['omniauth.auth']
end end
......
...@@ -87,7 +87,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -87,7 +87,7 @@ class Projects::BlobController < Projects::ApplicationController
private private
def blob def blob
@blob ||= @repository.blob_at(@commit.id, @path) @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
if @blob if @blob
@blob @blob
......
...@@ -56,6 +56,12 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -56,6 +56,12 @@ class Projects::BuildsController < Projects::ApplicationController
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha) render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end end
def erase
@build.erase(erased_by: current_user)
redirect_to namespace_project_build_path(project.namespace, project, @build),
notice: "Build has been sucessfully erased!"
end
private private
def build def build
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
# #
# Not to be confused with CommitsController, plural. # Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
include CreatesCommit
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds] before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
...@@ -9,6 +11,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -9,6 +11,7 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_read_commit_status!, only: [:builds] before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit before_action :commit
before_action :define_show_vars, only: [:show, :builds] before_action :define_show_vars, only: [:show, :builds]
before_action :authorize_edit_tree!, only: [:revert]
def show def show
apply_diff_view_cookie! apply_diff_view_cookie!
...@@ -55,8 +58,37 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -55,8 +58,37 @@ class Projects::CommitController < Projects::ApplicationController
render layout: false render layout: false
end end
def revert
assign_revert_commit_vars
return render_404 if @target_branch.blank?
create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.",
success_path: successful_revert_path, failure_path: failed_revert_path)
end
private private
def revert_type_title
@commit.merged_merge_request ? 'merge request' : 'commit'
end
def successful_revert_path
return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commits_url(@project.namespace, @project, @target_branch)
end
def failed_revert_path
return referenced_merge_request_url if @commit.merged_merge_request
namespace_project_commit_url(@project.namespace, @project, params[:id])
end
def referenced_merge_request_url
namespace_project_merge_request_url(@project.namespace, @project, @commit.merged_merge_request)
end
def commit def commit
@commit ||= @project.commit(params[:id]) @commit ||= @project.commit(params[:id])
end end
...@@ -79,4 +111,16 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -79,4 +111,16 @@ class Projects::CommitController < Projects::ApplicationController
@statuses = ci_commit.statuses if ci_commit @statuses = ci_commit.statuses if ci_commit
end end
def assign_revert_commit_vars
@commit = project.commit(params[:id])
@target_branch = params[:target_branch]
@mr_source_branch = @commit.revert_branch_name
@mr_target_branch = @target_branch
@commit_params = {
commit: @commit,
revert_type_title: revert_type_title,
create_merge_request: params[:create_merge_request].present? || different_project?
}
end
end end
...@@ -32,7 +32,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -32,7 +32,7 @@ class Projects::ForksController < Projects::ApplicationController
if continue_params if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice] redirect_to continue_params[:to], notice: continue_params[:notice]
else else
redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project was successfully forked." redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
end end
end end
else else
......
...@@ -32,6 +32,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -32,6 +32,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
@issues = @issues.page(params[:page]).per(PER_PAGE) @issues = @issues.page(params[:page]).per(PER_PAGE)
@label = @project.labels.find_by(title: params[:label_name])
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -69,7 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -69,7 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController
end end
def label_params def label_params
params.require(:label).permit(:title, :color) params.require(:label).permit(:title, :description, :color)
end end
def label def label
......
...@@ -34,6 +34,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -34,6 +34,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
@merge_requests = @merge_requests.preload(:target_project) @merge_requests = @merge_requests.preload(:target_project)
@label = @project.labels.find_by(title: params[:label_name])
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
...@@ -179,6 +181,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -179,6 +181,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return return
end end
TodoService.new.merge_merge_request(merge_request, current_user)
@merge_request.update(merge_error: nil) @merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active? if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
......
...@@ -35,6 +35,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -35,6 +35,7 @@ class Projects::MilestonesController < Projects::ApplicationController
@issues = @milestone.issues @issues = @milestone.issues
@users = @milestone.participants.uniq @users = @milestone.participants.uniq
@merge_requests = @milestone.merge_requests @merge_requests = @milestone.merge_requests
@labels = @milestone.labels
end end
def create def create
......
...@@ -236,7 +236,7 @@ class ProjectsController < ApplicationController ...@@ -236,7 +236,7 @@ class ProjectsController < ApplicationController
Emoji.emojis.map do |name, emoji| Emoji.emojis.map do |name, emoji|
{ {
name: name, name: name,
path: view_context.image_url("emoji/#{emoji["unicode"]}.png") path: view_context.image_url("#{emoji["unicode"]}.png")
} }
end end
end end
......
...@@ -119,6 +119,20 @@ class IssuableFinder ...@@ -119,6 +119,20 @@ class IssuableFinder
labels? && params[:label_name] == Label::None.title labels? && params[:label_name] == Label::None.title
end end
def labels
return @labels if defined?(@labels)
if labels? && !filter_by_no_label?
@labels = Label.where(title: label_names)
if projects
@labels = @labels.where(project: projects)
end
else
@labels = Label.none
end
end
def assignee? def assignee?
params[:assignee_id].present? params[:assignee_id].present?
end end
...@@ -253,8 +267,6 @@ class IssuableFinder ...@@ -253,8 +267,6 @@ class IssuableFinder
joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id"). joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
where(label_links: { id: nil }) where(label_links: { id: nil })
else else
label_names = params[:label_name].split(",")
items = items.joins(:labels).where(labels: { title: label_names }) items = items.joins(:labels).where(labels: { title: label_names })
if projects if projects
...@@ -266,6 +278,10 @@ class IssuableFinder ...@@ -266,6 +278,10 @@ class IssuableFinder
items items
end end
def label_names
params[:label_name].split(',')
end
def current_user_related? def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end end
......
# TodosFinder
#
# Used to filter Todos by set of params
#
# Arguments:
# current_user - which user use
# params:
# action_id: integer
# author_id: integer
# project_id; integer
# state: 'pending' or 'done'
# type: 'Issue' or 'MergeRequest'
#
class TodosFinder
NONE = '0'
attr_accessor :current_user, :params
def initialize(current_user, params)
@current_user = current_user
@params = params
end
def execute
items = current_user.todos
items = by_action_id(items)
items = by_author(items)
items = by_project(items)
items = by_state(items)
items = by_type(items)
items
end
private
def action_id?
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED].include?(action_id.to_i)
end
def action_id
params[:action_id]
end
def author?
params[:author_id].present?
end
def author
return @author if defined?(@author)
@author =
if author? && params[:author_id] != NONE
User.find(params[:author_id])
else
nil
end
end
def project?
params[:project_id].present?
end
def project
return @project if defined?(@project)
if project?
@project = Project.find(params[:project_id])
unless Ability.abilities.allowed?(current_user, :read_project, @project)
@project = nil
end
else
@project = nil
end
@project
end
def type?
type.present? && ['Issue', 'MergeRequest'].include?(type)
end
def type
params[:type]
end
def by_action_id(items)
if action_id?
items = items.where(action: action_id)
end
items
end
def by_author(items)
if author?
items = items.where(author_id: author.try(:id))
end
items
end
def by_project(items)
if project?
items = items.where(project: project)
end
items
end
def by_state(items)
case params[:state]
when 'done'
items.done
else
items.pending
end
end
def by_type(items)
if type?
items = items.where(target_type: type)
end
items
end
end
...@@ -118,12 +118,6 @@ module ApplicationHelper ...@@ -118,12 +118,6 @@ module ApplicationHelper
grouped_options_for_select(options, @ref || @project.default_branch) grouped_options_for_select(options, @ref || @project.default_branch)
end end
def emoji_autocomplete_source
# should be an array of strings
# so to_s can be called, because it is sufficient and to_json is too slow
Emoji.names.to_s
end
# Define whenever show last push event # Define whenever show last push event
# with suggestion to create MR # with suggestion to create MR
def show_last_push_widget?(event) def show_last_push_widget?(event)
......
...@@ -127,10 +127,6 @@ module BlobHelper ...@@ -127,10 +127,6 @@ module BlobHelper
end end
end end
def blob_svg?(blob)
blob.language && blob.language.name == 'SVG'
end
# SVGs can contain malicious JavaScript; only include whitelisted # SVGs can contain malicious JavaScript; only include whitelisted
# elements and attributes. Note that this whitelist is by no means complete # elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements. # and may omit some elements.
......
...@@ -123,6 +123,37 @@ module CommitsHelper ...@@ -123,6 +123,37 @@ module CommitsHelper
) )
end end
def revert_commit_link(commit, continue_to_path, btn_class: nil)
return unless current_user
tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request"
if can_collaborate_with_project?
content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do
link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}"
end
elsif can?(current_user, :fork_project, @project)
continue_params = {
to: continue_to_path,
notice: edit_in_new_fork_notice + ' Try to revert this commit again.',
notice_now: edit_in_new_fork_notice_now
}
fork_path = namespace_project_forks_path(@project.namespace, @project,
namespace_key: current_user.namespace.id,
continue: continue_params)
link_to 'Revert', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', title: tooltip
end
end
def revert_commit_type(commit)
if commit.merged_merge_request
'merge request'
else
'commit'
end
end
protected protected
# Private: Returns a link to a person. If the person has a matching user and # Private: Returns a link to a person. If the person has a matching user and
......
...@@ -137,7 +137,7 @@ module DiffHelper ...@@ -137,7 +137,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button # Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format) params_copy.delete(:format)
link_to url_for(params_copy), id: "#{name}-diff-btn", class: (selected ? 'btn active' : 'btn') do link_to url_for(params_copy), id: "#{name}-diff-btn", class: (selected ? 'btn active' : 'btn'), data: { view_type: name } do
title title
end end
end end
......
...@@ -23,6 +23,7 @@ module NavHelper ...@@ -23,6 +23,7 @@ module NavHelper
if current_path?('merge_requests#show') || if current_path?('merge_requests#show') ||
current_path?('merge_requests#diffs') || current_path?('merge_requests#diffs') ||
current_path?('merge_requests#commits') || current_path?('merge_requests#commits') ||
current_path?('merge_requests#builds') ||
current_path?('issues#show') current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed" "page-gutter right-sidebar-collapsed"
......
module TodosHelper
def todos_pending_count
current_user.todos.pending.count
end
def todos_done_count
current_user.todos.done.count
end
def todo_action_name(todo)
case todo.action
when Todo::ASSIGNED then 'assigned you'
when Todo::MENTIONED then 'mentioned you on'
end
end
def todo_target_link(todo)
target = todo.target_type.titleize.downcase
link_to "#{target} #{todo.target.to_reference}", todo_target_path(todo)
end
def todo_target_path(todo)
anchor = dom_id(todo.note) if todo.note.present?
polymorphic_path([todo.project.namespace.becomes(Namespace),
todo.project, todo.target], anchor: anchor)
end
def todos_filter_params
{
state: params[:state],
project_id: params[:project_id],
author_id: params[:author_id],
type: params[:type],
action_id: params[:action_id],
}
end
def todos_filter_path(options = {})
without = options.delete(:without)
options = todos_filter_params.merge(options)
if without.present?
without.each do |key|
options.delete(key)
end
end
path = request.path
path << "?#{options.to_param}"
path
end
def todo_actions_options
actions = [
OpenStruct.new(id: '', title: 'Any Action'),
OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'),
OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned')
]
options_from_collection_for_select(actions, 'id', 'title', params[:action_id])
end
def todo_projects_options
projects = current_user.authorized_projects.sorted_by_activity.non_archived
projects = projects.includes(:namespace)
projects = projects.map do |project|
OpenStruct.new(id: project.id, title: project.name_with_namespace)
end
projects.unshift(OpenStruct.new(id: '', title: 'Any Project'))
options_from_collection_for_select(projects, 'id', 'title', params[:project_id])
end
def todo_types_options
types = [
OpenStruct.new(title: 'Any Type', name: ''),
OpenStruct.new(title: 'Issue', name: 'Issue'),
OpenStruct.new(title: 'Merge Request', name: 'MergeRequest')
]
options_from_collection_for_select(types, 'name', 'title', params[:type])
end
end
...@@ -56,8 +56,7 @@ module TreeHelper ...@@ -56,8 +56,7 @@ module TreeHelper
return false unless on_top_of_branch?(project, ref) return false unless on_top_of_branch?(project, ref)
can?(current_user, :push_code, project) || can_collaborate_with_project?(project)
(current_user && current_user.already_forked?(project))
end end
def tree_edit_branch(project = @project, ref = @ref) def tree_edit_branch(project = @project, ref = @ref)
......
# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
class Blob < SimpleDelegator
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
#
# This method prevents the decorated object from evaluating to "truthy" when
# given a nil value. For example:
#
# blob = Blob.new(nil)
# puts "truthy" if blob # => "truthy"
#
# blob = Blob.decorate(nil)
# puts "truthy" if blob # No output
def self.decorate(blob)
return if blob.nil?
new(blob)
end
def svg?
text? && language && language.name == 'SVG'
end
def to_partial_path
if lfs_pointer?
'download'
elsif image? || svg?
'image'
elsif text?
'text'
else
'download'
end
end
end
...@@ -31,15 +31,19 @@ ...@@ -31,15 +31,19 @@
# artifacts_file :text # artifacts_file :text
# gl_project_id :integer # gl_project_id :integer
# artifacts_metadata :text # artifacts_metadata :text
# erased_by_id :integer
# erased_at :datetime
# #
module Ci module Ci
class Build < CommitStatus class Build < CommitStatus
include Gitlab::Application.routes.url_helpers include Gitlab::Application.routes.url_helpers
LAZY_ATTRIBUTES = ['trace'] LAZY_ATTRIBUTES = ['trace']
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'
belongs_to :erased_by, class_name: 'User'
serialize :options serialize :options
...@@ -103,23 +107,22 @@ module Ci ...@@ -103,23 +107,22 @@ module Ci
end end
state_machine :status, initial: :pending do state_machine :status, initial: :pending do
after_transition pending: :running do |build, transition| after_transition pending: :running do |build|
build.execute_hooks build.execute_hooks
end end
after_transition any => [:success, :failed, :canceled] do |build, transition| # We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
return unless build.project around_transition any => [:success, :failed, :canceled] do |build, block|
block.call
build.commit.create_next_builds(build) if build.commit
end
after_transition any => [:success, :failed, :canceled] do |build|
build.update_coverage build.update_coverage
build.commit.create_next_builds(build)
build.execute_hooks build.execute_hooks
end end
end end
def ignored?
failed? && allow_failure?
end
def retryable? def retryable?
project.builds_enabled? && commands.present? project.builds_enabled? && commands.present?
end end
...@@ -179,6 +182,7 @@ module Ci ...@@ -179,6 +182,7 @@ module Ci
end end
def update_coverage def update_coverage
return unless project
coverage_regex = project.build_coverage_regex coverage_regex = project.build_coverage_regex
return unless coverage_regex return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex) coverage = extract_coverage(trace, coverage_regex)
...@@ -203,6 +207,10 @@ module Ci ...@@ -203,6 +207,10 @@ module Ci
end end
end end
def has_trace?
raw_trace.present?
end
def raw_trace def raw_trace
if File.file?(path_to_trace) if File.file?(path_to_trace)
File.read(path_to_trace) File.read(path_to_trace)
...@@ -330,6 +338,7 @@ module Ci ...@@ -330,6 +338,7 @@ module Ci
end end
def execute_hooks def execute_hooks
return unless project
build_data = Gitlab::BuildDataBuilder.build(self) build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks) project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks) project.execute_services(build_data.dup, :build_hooks)
...@@ -359,6 +368,33 @@ module Ci ...@@ -359,6 +368,33 @@ module Ci
Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
end end
def erase(opts = {})
return false unless erasable?
remove_artifacts_file!
remove_artifacts_metadata!
erase_trace!
update_erased!(opts[:erased_by])
end
def erasable?
complete? && (artifacts? || has_trace?)
end
def erased?
!self.erased_at.nil?
end
private
def erase_trace!
self.trace = nil
end
def update_erased!(user = nil)
self.update(erased_by: user, erased_at: Time.now)
end
private private
def yaml_variables def yaml_variables
......
...@@ -22,6 +22,7 @@ module Ci ...@@ -22,6 +22,7 @@ module Ci
extend Ci::Model extend Ci::Model
LAST_CONTACT_TIME = 5.minutes.ago LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
...@@ -38,6 +39,11 @@ module Ci ...@@ -38,6 +39,11 @@ module Ci
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) } scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
scope :ordered, ->() { order(id: :desc) } scope :ordered, ->() { order(id: :desc) }
scope :owned_or_shared, ->(project_id) do
joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end
acts_as_taggable acts_as_taggable
def self.search(query) def self.search(query)
......
...@@ -215,6 +215,44 @@ class Commit ...@@ -215,6 +215,44 @@ class Commit
ci_commit.try(:status) || :not_found ci_commit.try(:status) || :not_found
end end
def revert_branch_name
"revert-#{short_id}"
end
def revert_description
if merged_merge_request
"This reverts merge request #{merged_merge_request.to_reference}"
else
"This reverts commit #{sha}"
end
end
def revert_message
%Q{Revert "#{title}"\n\n#{revert_description}}
end
def reverts_commit?(commit)
description.include?(commit.revert_description)
end
def merge_commit?
parents.size > 1
end
def merged_merge_request
return @merged_merge_request if defined?(@merged_merge_request)
@merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit?
end
def has_been_reverted?(current_user = nil, noteable = self)
Gitlab::ReferenceExtractor.lazily do
noteable.notes.system.flat_map do |note|
note.all_references(current_user).commits
end
end.any? { |commit_ref| commit_ref.reverts_commit?(self) }
end
private private
def repo_changes def repo_changes
......
...@@ -75,16 +75,16 @@ class CommitStatus < ActiveRecord::Base ...@@ -75,16 +75,16 @@ class CommitStatus < ActiveRecord::Base
transition [:pending, :running] => :canceled transition [:pending, :running] => :canceled
end end
after_transition pending: :running do |build, transition| after_transition pending: :running do |commit_status|
build.update_attributes started_at: Time.now commit_status.update_attributes started_at: Time.now
end end
after_transition any => [:success, :failed, :canceled] do |build, transition| after_transition any => [:success, :failed, :canceled] do |commit_status|
build.update_attributes finished_at: Time.now commit_status.update_attributes finished_at: Time.now
end end
after_transition [:pending, :running] => :success do |build, transition| after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end end
state :pending, value: 'pending' state :pending, value: 'pending'
...@@ -113,6 +113,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -113,6 +113,10 @@ class CommitStatus < ActiveRecord::Base
canceled? || success? || failed? canceled? || success? || failed?
end end
def ignored?
failed? && allow_failure?
end
def duration def duration
if started_at && finished_at if started_at && finished_at
finished_at - started_at finished_at - started_at
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# template :boolean default(FALSE) # template :boolean default(FALSE)
# description :string(255)
# #
class Label < ActiveRecord::Base class Label < ActiveRecord::Base
...@@ -85,6 +86,10 @@ class Label < ActiveRecord::Base ...@@ -85,6 +86,10 @@ class Label < ActiveRecord::Base
issues.opened.count issues.opened.count
end end
def closed_issues_count
issues.closed.count
end
def template? def template?
template template
end end
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
# merge_params :text # merge_params :text
# merge_when_build_succeeds :boolean default(FALSE), not null # merge_when_build_succeeds :boolean default(FALSE), not null
# merge_user_id :integer # merge_user_id :integer
# merge_commit_sha :string
# #
require Rails.root.join("app/models/commit") require Rails.root.join("app/models/commit")
...@@ -532,4 +533,12 @@ class MergeRequest < ActiveRecord::Base ...@@ -532,4 +533,12 @@ class MergeRequest < ActiveRecord::Base
[diff_base_commit, last_commit] [diff_base_commit, last_commit]
end end
def merge_commit
@merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
end
def can_be_reverted?(current_user = nil)
merge_commit && !merge_commit.has_been_reverted?(current_user, self)
end
end end
...@@ -27,6 +27,7 @@ class Milestone < ActiveRecord::Base ...@@ -27,6 +27,7 @@ class Milestone < ActiveRecord::Base
belongs_to :project belongs_to :project
has_many :issues has_many :issues
has_many :labels, through: :issues
has_many :merge_requests has_many :merge_requests
has_many :participants, through: :issues, source: :assignee has_many :participants, through: :issues, source: :assignee
...@@ -109,6 +110,19 @@ class Milestone < ActiveRecord::Base ...@@ -109,6 +110,19 @@ class Milestone < ActiveRecord::Base
0 0
end end
# Returns the elapsed time (in percent) since the Milestone creation date until today.
# If the Milestone doesn't have a due_date then returns 0 since we can't calculate the elapsed time.
# If the Milestone is overdue then it returns 100%.
def percent_time_used
return 0 unless due_date
return 100 if expired?
duration = ((created_at - due_date.to_datetime) / 1.day)
days_elapsed = ((created_at - Time.now) / 1.day)
((days_elapsed.to_f / duration) * 100).floor
end
def expires_at def expires_at
if due_date if due_date
if due_date.past? if due_date.past?
......
...@@ -37,6 +37,8 @@ class Note < ActiveRecord::Base ...@@ -37,6 +37,8 @@ class Note < ActiveRecord::Base
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User" belongs_to :updated_by, class_name: "User"
has_many :todos, dependent: :destroy
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
...@@ -375,6 +377,7 @@ class Note < ActiveRecord::Base ...@@ -375,6 +377,7 @@ class Note < ActiveRecord::Base
# #
def set_award! def set_award!
return unless awards_supported? && contains_emoji_only? return unless awards_supported? && contains_emoji_only?
self.is_award = true self.is_award = true
self.note = award_emoji_name self.note = award_emoji_name
end end
...@@ -382,7 +385,7 @@ class Note < ActiveRecord::Base ...@@ -382,7 +385,7 @@ class Note < ActiveRecord::Base
private private
def awards_supported? def awards_supported?
noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest) (noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)) && !for_diff_line?
end end
def contains_emoji_only? def contains_emoji_only?
......
...@@ -136,7 +136,7 @@ class ProjectTeam ...@@ -136,7 +136,7 @@ class ProjectTeam
end end
def human_max_access(user_id) def human_max_access(user_id)
Gitlab::Access.options.key max_member_access(user_id) Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end end
# This method assumes project and group members are eager loaded for optimal # This method assumes project and group members are eager loaded for optimal
......
...@@ -23,13 +23,11 @@ class Repository ...@@ -23,13 +23,11 @@ class Repository
def raw_repository def raw_repository
return nil unless path_with_namespace return nil unless path_with_namespace
@raw_repository ||= begin @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
repo = Gitlab::Git::Repository.new(path_to_repo)
repo.autocrlf = :input
repo
rescue Gitlab::Git::Repository::NoRepository
nil
end end
def update_autocrlf_option
raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
end end
# Return absolute path to repository # Return absolute path to repository
...@@ -40,7 +38,12 @@ class Repository ...@@ -40,7 +38,12 @@ class Repository
end end
def exists? def exists?
raw_repository return false unless raw_repository
raw_repository.rugged
true
rescue Gitlab::Git::Repository::NoRepository
false
end end
def empty? def empty?
...@@ -67,7 +70,7 @@ class Repository ...@@ -67,7 +70,7 @@ class Repository
end end
def commit(id = 'HEAD') def commit(id = 'HEAD')
return nil unless raw_repository return nil unless exists?
commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit, @project) if commit commit = Commit.new(commit, @project) if commit
commit commit
...@@ -236,6 +239,10 @@ class Repository ...@@ -236,6 +239,10 @@ class Repository
end end
expire_branch_cache(branch_name) expire_branch_cache(branch_name)
# This ensures this particular cache is flushed after the first commit to a
# new repository.
expire_emptiness_caches if empty?
end end
# Expires _all_ caches, including those that would normally only be expired # Expires _all_ caches, including those that would normally only be expired
...@@ -616,6 +623,34 @@ class Repository ...@@ -616,6 +623,34 @@ class Repository
end end
end end
def revert(user, commit, base_branch, target_branch = nil)
source_sha = find_branch(base_branch).target
target_branch ||= base_branch
args = [commit.id, source_sha]
args << { mainline: 1 } if commit.merge_commit?
revert_index = rugged.revert_commit(*args)
return false if revert_index.conflicts?
tree_id = revert_index.write_tree(rugged)
return false unless diff_exists?(source_sha, tree_id)
commit_with_hooks(user, target_branch) do |ref|
committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message,
author: committer,
committer: committer,
tree: tree_id,
parents: [rugged.lookup(source_sha)],
update_ref: ref)
end
end
def diff_exists?(sha1, sha2)
rugged.diff(sha1, sha2).size > 0
end
def merged_to_root_ref?(branch_name) def merged_to_root_ref?(branch_name)
branch_commit = commit(branch_name) branch_commit = commit(branch_name)
root_ref_commit = commit(root_ref) root_ref_commit = commit(root_ref)
...@@ -693,12 +728,15 @@ class Repository ...@@ -693,12 +728,15 @@ class Repository
end end
def commit_with_hooks(current_user, branch) def commit_with_hooks(current_user, branch)
update_autocrlf_option
oldrev = Gitlab::Git::BLANK_SHA oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
target_branch = find_branch(branch)
was_empty = empty? was_empty = empty?
unless was_empty if !was_empty && target_branch
oldrev = find_branch(branch).target oldrev = target_branch.target
end end
with_tmp_ref(oldrev) do |tmp_ref| with_tmp_ref(oldrev) do |tmp_ref|
...@@ -710,7 +748,7 @@ class Repository ...@@ -710,7 +748,7 @@ class Repository
end end
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty if was_empty || !target_branch
# Create branch # Create branch
rugged.references.create(ref, newrev) rugged.references.create(ref, newrev)
else else
...@@ -725,6 +763,8 @@ class Repository ...@@ -725,6 +763,8 @@ class Repository
end end
end end
end end
newrev
end end
end end
......
# == Schema Information
#
# Table name: todos
#
# id :integer not null, primary key
# user_id :integer not null
# project_id :integer not null
# target_id :integer not null
# target_type :string not null
# author_id :integer
# note_id :integer
# action :integer not null
# state :string not null
# created_at :datetime
# updated_at :datetime
#
class Todo < ActiveRecord::Base
ASSIGNED = 1
MENTIONED = 2
belongs_to :author, class_name: "User"
belongs_to :note
belongs_to :project
belongs_to :target, polymorphic: true, touch: true
belongs_to :user
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :action, :project, :target, :user, presence: true
default_scope { reorder(id: :desc) }
scope :pending, -> { with_state(:pending) }
scope :done, -> { with_state(:done) }
state_machine :state, initial: :pending do
event :done do
transition pending: :done
end
state :pending
state :done
end
def body
if note.present?
note.note
else
target.title
end
end
end
...@@ -140,7 +140,7 @@ class User < ActiveRecord::Base ...@@ -140,7 +140,7 @@ class User < ActiveRecord::Base
has_one :abuse_report, dependent: :destroy has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :todos, dependent: :destroy
# #
# Validations # Validations
......
...@@ -23,6 +23,10 @@ class BaseService ...@@ -23,6 +23,10 @@ class BaseService
EventCreateService.new EventCreateService.new
end end
def todo_service
TodoService.new
end
def log_info(message) def log_info(message)
Gitlab::AppLogger.info message Gitlab::AppLogger.info message
end end
......
...@@ -34,6 +34,7 @@ module Ci ...@@ -34,6 +34,7 @@ module Ci
build = commit.builds.create!(build_attrs) build = commit.builds.create!(build_attrs)
build.execute_hooks build.execute_hooks
build
end end
end end
end end
......
module Commits
class RevertService < ::BaseService
class ValidationError < StandardError; end
class ReversionError < StandardError; end
def execute
@source_project = params[:source_project] || @project
@target_branch = params[:target_branch]
@commit = params[:commit]
@create_merge_request = params[:create_merge_request].present?
validate and commit
rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
ValidationError, ReversionError => ex
error(ex.message)
end
def commit
revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
if @create_merge_request
# Temporary branch exists and contains the revert commit
return success if repository.find_branch(revert_into)
create_target_branch
end
unless repository.revert(current_user, @commit, revert_into)
error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically.
It may have already been reverted, or a more recent commit may have updated some of its content."
raise ReversionError, error_msg
end
success
end
private
def create_target_branch
result = CreateBranchService.new(@project, current_user)
.execute(@commit.revert_branch_name, @target_branch, source_project: @source_project)
if result[:status] == :error
raise ReversionError, "There was an error creating the source branch: #{result[:message]}"
end
end
def validate
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
unless allowed
raise_error('You are not allowed to push into this branch')
end
true
end
end
end
...@@ -54,7 +54,7 @@ class IssuableBaseService < BaseService ...@@ -54,7 +54,7 @@ class IssuableBaseService < BaseService
if params.present? && issuable.update_attributes(params.merge(updated_by: current_user)) if params.present? && issuable.update_attributes(params.merge(updated_by: current_user))
issuable.reset_events_cache issuable.reset_events_cache
handle_common_system_notes(issuable, old_labels: old_labels) handle_common_system_notes(issuable, old_labels: old_labels)
handle_changes(issuable) handle_changes(issuable, old_labels: old_labels)
issuable.create_new_cross_references!(current_user) issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update') execute_hooks(issuable, 'update')
end end
...@@ -71,6 +71,19 @@ class IssuableBaseService < BaseService ...@@ -71,6 +71,19 @@ class IssuableBaseService < BaseService
end end
end end
def has_changes?(issuable, options = {})
valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch]
attrs_changed = valid_attrs.any? do |attr|
issuable.previous_changes.include?(attr.to_s)
end
old_labels = options[:old_labels]
labels_changed = old_labels && issuable.labels != old_labels
attrs_changed || labels_changed
end
def handle_common_system_notes(issuable, options = {}) def handle_common_system_notes(issuable, options = {})
if issuable.previous_changes.include?('title') if issuable.previous_changes.include?('title')
create_title_change_note(issuable, issuable.previous_changes['title'].first) create_title_change_note(issuable, issuable.previous_changes['title'].first)
......
...@@ -3,6 +3,7 @@ module Issues ...@@ -3,6 +3,7 @@ module Issues
def execute(issue, commit = nil) def execute(issue, commit = nil)
if project.jira_tracker? && project.jira_service.active if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(commit, issue) project.jira_service.execute(commit, issue)
todo_service.close_issue(issue, current_user)
return issue return issue
end end
...@@ -10,6 +11,7 @@ module Issues ...@@ -10,6 +11,7 @@ module Issues
event_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user)
create_note(issue, commit) create_note(issue, commit)
notification_service.close_issue(issue, current_user) notification_service.close_issue(issue, current_user)
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close') execute_hooks(issue, 'close')
end end
......
...@@ -9,6 +9,7 @@ module Issues ...@@ -9,6 +9,7 @@ module Issues
if issue.save if issue.save
issue.update_attributes(label_ids: label_params) issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user) notification_service.new_issue(issue, current_user)
todo_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user) event_service.open_issue(issue, current_user)
issue.create_cross_references!(current_user) issue.create_cross_references!(current_user)
execute_hooks(issue, 'open') execute_hooks(issue, 'open')
......
...@@ -4,7 +4,16 @@ module Issues ...@@ -4,7 +4,16 @@ module Issues
update(issue) update(issue)
end end
def handle_changes(issue) def handle_changes(issue, options = {})
if has_changes?(issue, options)
todo_service.mark_pending_todos_as_done(issue, current_user)
end
if issue.previous_changes.include?('title') ||
issue.previous_changes.include?('description')
todo_service.update_issue(issue, current_user)
end
if issue.previous_changes.include?('milestone_id') if issue.previous_changes.include?('milestone_id')
create_milestone_note(issue) create_milestone_note(issue)
end end
...@@ -12,6 +21,7 @@ module Issues ...@@ -12,6 +21,7 @@ module Issues
if issue.previous_changes.include?('assignee_id') if issue.previous_changes.include?('assignee_id')
create_assignee_note(issue) create_assignee_note(issue)
notification_service.reassigned_issue(issue, current_user) notification_service.reassigned_issue(issue, current_user)
todo_service.reassigned_issue(issue, current_user)
end end
end end
......
...@@ -56,7 +56,7 @@ module MergeRequests ...@@ -56,7 +56,7 @@ module MergeRequests
if commits && commits.count == 1 if commits && commits.count == 1
commit = commits.first commit = commits.first
merge_request.title = commit.title merge_request.title = commit.title
merge_request.description = commit.description.try(:strip) merge_request.description ||= commit.description.try(:strip)
else else
merge_request.title = merge_request.source_branch.titleize.humanize merge_request.title = merge_request.source_branch.titleize.humanize
end end
......
...@@ -9,6 +9,7 @@ module MergeRequests ...@@ -9,6 +9,7 @@ module MergeRequests
event_service.close_mr(merge_request, current_user) event_service.close_mr(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
notification_service.close_mr(merge_request, current_user) notification_service.close_mr(merge_request, current_user)
todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close') execute_hooks(merge_request, 'close')
end end
......
...@@ -18,6 +18,7 @@ module MergeRequests ...@@ -18,6 +18,7 @@ module MergeRequests
merge_request.update_attributes(label_ids: label_params) merge_request.update_attributes(label_ids: label_params)
event_service.open_mr(merge_request, current_user) event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user) notification_service.new_merge_request(merge_request, current_user)
todo_service.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(current_user) merge_request.create_cross_references!(current_user)
execute_hooks(merge_request) execute_hooks(merge_request)
end end
......
...@@ -34,7 +34,8 @@ module MergeRequests ...@@ -34,7 +34,8 @@ module MergeRequests
committer: committer committer: committer
} }
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options) commit_id = repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
merge_request.update(merge_commit_sha: commit_id)
rescue StandardError => e rescue StandardError => e
merge_request.update(merge_error: "Something went wrong during merge") merge_request.update(merge_error: "Something went wrong during merge")
Rails.logger.error(e.message) Rails.logger.error(e.message)
......
...@@ -19,8 +19,8 @@ module MergeRequests ...@@ -19,8 +19,8 @@ module MergeRequests
end end
# Triggers the automatic merge of merge_request once the build succeeds # Triggers the automatic merge of merge_request once the build succeeds
def trigger(build) def trigger(commit_status)
merge_requests = merge_request_from(build) merge_requests = merge_request_from(commit_status)
merge_requests.each do |merge_request| merge_requests.each do |merge_request|
next unless merge_request.merge_when_build_succeeds? next unless merge_request.merge_when_build_succeeds?
...@@ -45,9 +45,14 @@ module MergeRequests ...@@ -45,9 +45,14 @@ module MergeRequests
private private
def merge_request_from(build) def merge_request_from(commit_status)
merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a branches = commit_status.ref
merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a
# This is for ref-less builds
branches ||= @project.repository.branch_names_contains(commit_status.sha)
merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
merge_requests.uniq.select(&:source_project) merge_requests.uniq.select(&:source_project)
end end
......
...@@ -14,7 +14,16 @@ module MergeRequests ...@@ -14,7 +14,16 @@ module MergeRequests
update(merge_request) update(merge_request)
end end
def handle_changes(merge_request) def handle_changes(merge_request, options = {})
if has_changes?(merge_request, options)
todo_service.mark_pending_todos_as_done(merge_request, current_user)
end
if merge_request.previous_changes.include?('title') ||
merge_request.previous_changes.include?('description')
todo_service.update_merge_request(merge_request, current_user)
end
if merge_request.previous_changes.include?('target_branch') if merge_request.previous_changes.include?('target_branch')
create_branch_change_note(merge_request, 'target', create_branch_change_note(merge_request, 'target',
merge_request.previous_changes['target_branch'].first, merge_request.previous_changes['target_branch'].first,
...@@ -28,6 +37,7 @@ module MergeRequests ...@@ -28,6 +37,7 @@ module MergeRequests
if merge_request.previous_changes.include?('assignee_id') if merge_request.previous_changes.include?('assignee_id')
create_assignee_note(merge_request) create_assignee_note(merge_request)
notification_service.reassigned_merge_request(merge_request, current_user) notification_service.reassigned_merge_request(merge_request, current_user)
todo_service.reassigned_merge_request(merge_request, current_user)
end end
if merge_request.previous_changes.include?('target_branch') || if merge_request.previous_changes.include?('target_branch') ||
......
...@@ -8,6 +8,7 @@ module Notes ...@@ -8,6 +8,7 @@ module Notes
if note.save if note.save
# Finish the harder work in the background # Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params) NewNoteWorker.perform_in(2.seconds, note.id, params)
TodoService.new.new_note(note, current_user)
end end
note note
......
module Notes module Notes
class PostProcessService class PostProcessService
attr_accessor :note attr_accessor :note
def initialize(note) def initialize(note)
...@@ -25,6 +24,5 @@ module Notes ...@@ -25,6 +24,5 @@ module Notes
@note.project.execute_hooks(note_data, :note_hooks) @note.project.execute_hooks(note_data, :note_hooks)
@note.project.execute_services(note_data, :note_hooks) @note.project.execute_services(note_data, :note_hooks)
end end
end end
end end
...@@ -7,6 +7,10 @@ module Notes ...@@ -7,6 +7,10 @@ module Notes
note.create_new_cross_references!(current_user) note.create_new_cross_references!(current_user)
note.reset_events_cache note.reset_events_cache
if note.previous_changes.include?('note')
TodoService.new.update_note(note, current_user)
end
note note
end end
end end
......
# TodoService class
#
# Used for creating todos after certain user actions
#
# Ex.
# TodoService.new.new_issue(issue, current_user)
#
class TodoService
# When create an issue we should:
#
# * create a todo for assignee if issue is assigned
# * create a todo for each mentioned user on issue
#
def new_issue(issue, current_user)
new_issuable(issue, current_user)
end
# When update an issue we should:
#
# * mark all pending todos related to the issue for the current user as done
#
def update_issue(issue, current_user)
create_mention_todos(issue.project, issue, current_user)
end
# When close an issue we should:
#
# * mark all pending todos related to the target for the current user as done
#
def close_issue(issue, current_user)
mark_pending_todos_as_done(issue, current_user)
end
# When we reassign an issue we should:
#
# * create a pending todo for new assignee if issue is assigned
#
def reassigned_issue(issue, current_user)
create_assignment_todo(issue, current_user)
end
# When create a merge request we should:
#
# * creates a pending todo for assignee if merge request is assigned
# * create a todo for each mentioned user on merge request
#
def new_merge_request(merge_request, current_user)
new_issuable(merge_request, current_user)
end
# When update a merge request we should:
#
# * create a todo for each mentioned user on merge request
#
def update_merge_request(merge_request, current_user)
create_mention_todos(merge_request.project, merge_request, current_user)
end
# When close a merge request we should:
#
# * mark all pending todos related to the target for the current user as done
#
def close_merge_request(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user)
end
# When we reassign a merge request we should:
#
# * creates a pending todo for new assignee if merge request is assigned
#
def reassigned_merge_request(merge_request, current_user)
create_assignment_todo(merge_request, current_user)
end
# When merge a merge request we should:
#
# * mark all pending todos related to the target for the current user as done
#
def merge_merge_request(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user)
end
# When create a note we should:
#
# * mark all pending todos related to the noteable for the note author as done
# * create a todo for each mentioned user on note
#
def new_note(note, current_user)
handle_note(note, current_user)
end
# When update a note we should:
#
# * mark all pending todos related to the noteable for the current user as done
# * create a todo for each new user mentioned on note
#
def update_note(note, current_user)
handle_note(note, current_user)
end
# When marking pending todos as done we should:
#
# * mark all pending todos related to the target for the current user as done
#
def mark_pending_todos_as_done(target, user)
pending_todos(user, target.project, target).update_all(state: :done)
end
private
def create_todos(project, target, author, users, action, note = nil)
Array(users).each do |user|
next if pending_todos(user, project, target).exists?
Todo.create(
project: project,
user_id: user.id,
author_id: author.id,
target_id: target.id,
target_type: target.class.name,
action: action,
note: note
)
end
end
def new_issuable(issuable, author)
create_assignment_todo(issuable, author)
create_mention_todos(issuable.project, issuable, author)
end
def handle_note(note, author)
# Skip system notes, like status changes and cross-references
return if note.system
project = note.project
target = note.noteable
mark_pending_todos_as_done(target, author)
create_mention_todos(project, target, author, note)
end
def create_assignment_todo(issuable, author)
if issuable.assignee && issuable.assignee != author
create_todos(issuable.project, issuable, author, issuable.assignee, Todo::ASSIGNED)
end
end
def create_mention_todos(project, issuable, author, note = nil)
mentioned_users = filter_mentioned_users(project, note || issuable, author)
create_todos(project, issuable, author, mentioned_users, Todo::MENTIONED, note)
end
def filter_mentioned_users(project, target, author)
mentioned_users = target.mentioned_users.select do |user|
user.can?(:read_project, project)
end
mentioned_users.delete(author)
mentioned_users.uniq
end
def pending_todos(user, project, target)
user.todos.pending.where(
project_id: project.id,
target_id: target.id,
target_type: target.class.name
)
end
end
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :title, class: "form-control", required: true = f.text_field :title, class: "form-control", required: true
.form-group
= f.label :description, class: 'control-label'
.col-sm-10
= f.text_field :description, class: "form-control js-quick-submit"
.form-group .form-group
= f.label :color, "Background color", class: 'control-label' = f.label :color, "Background color", class: 'control-label'
.col-sm-10 .col-sm-10
......
%li{id: dom_id(label)} %li{id: dom_id(label)}
.label-row
= render_colored_label(label) = render_colored_label(label)
= markdown(label.description, pipeline: :single_line)
.pull-right .pull-right
= link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm' = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
= link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"} = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
%br %br
- if current_user.can_create_project? - if current_user.can_create_project?
You can create up to You can create up to
%strong= pluralize(current_user.projects_limit, "project") + "." %strong= pluralize(number_with_delimiter(current_user.projects_limit), "project") + "."
- else - else
If you are added to a project, it will be displayed here. If you are added to a project, it will be displayed here.
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
.dashboard-intro-text .dashboard-intro-text
%p.slead %p.slead
There are There are
%strong= publicish_project_count %strong= number_with_delimiter(publicish_project_count)
public projects on this server. public projects on this server.
%br %br
Public projects are an easy way to allow everyone to have read-only access. Public projects are an easy way to allow everyone to have read-only access.
......
%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
.todo-item{class: 'todo-block'}
= image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
.todo-title
%span.author_name
= link_to_author todo
%span.todo_label
= todo_action_name(todo)
= todo_target_link(todo)
&middot; #{time_ago_with_tooltip(todo.created_at)}
- if todo.pending?
.todo-actions.pull-right
= link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn'
.todo-body
.todo-note
.md
= event_note(todo.body, project: todo.project)
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
.top-area
%ul.nav-links
%li{class: ('active' if params[:state].blank? || params[:state] == 'pending')}
= link_to todos_filter_path(state: 'pending') do
%span
To do
%span{class: 'badge'}
= todos_pending_count
%li{class: ('active' if params[:state] == 'done')}
= link_to todos_filter_path(state: 'done') do
%span
Done
%span{class: 'badge'}
= todos_done_count
.nav-controls
- if @todos.any?(&:pending?)
= link_to 'Mark all as done', destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn', method: :delete
.todos-filters
.gray-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline
= select_tag('project_id', todo_projects_options,
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Project'})
.filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id],
placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
.filter-item.inline
= select_tag('type', todo_types_options,
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Type'})
.filter-item.inline.actions-filter
= select_tag('action_id', todo_actions_options,
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Action'})
.prepend-top-default
- if @todos.any?
- @todos.group_by(&:project).each do |group|
.panel.panel-default.panel-small
- project = group[0]
.panel-heading
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
%ul.well-list.todos-list
= render group[1]
= paginate @todos, theme: "gitlab"
- else
.nothing-here-block You're all done!
:javascript
new UsersSelect();
$('form.filter-form').on('submit', function (event) {
event.preventDefault();
Turbolinks.visit(this.action + '&' + $(this).serialize());
});
...@@ -21,6 +21,10 @@ ...@@ -21,6 +21,10 @@
%li %li
= link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw') = icon('wrench fw')
%li
= link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
%span.badge.todos-pending-count
= todos_pending_count
- if current_user.can_create_project? - if current_user.can_create_project?
%li %li
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
......
...@@ -4,6 +4,12 @@ ...@@ -4,6 +4,12 @@
= icon('home fw') = icon('home fw')
%span %span
Projects Projects
= nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do
= icon('bell fw')
%span
Todos
%span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do = nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
= icon('dashboard fw') = icon('dashboard fw')
......
...@@ -3,7 +3,12 @@ ...@@ -3,7 +3,12 @@
.project-identicon-holder .project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90') = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc .project-home-desc
%h1= @project.name %h1
= @project.name
%span.visibility-icon.has_tooltip{data: { container: 'body' },
title: "#{visibility_level_label(@project.visibility_level)} - #{project_visibility_level_description(@project.visibility_level)}"}
= visibility_level_icon(@project.visibility_level, fw: false)
- if @project.description.present? - if @project.description.present?
= markdown(@project.description, pipeline: :description) = markdown(@project.description, pipeline: :description)
...@@ -12,10 +17,6 @@ ...@@ -12,10 +17,6 @@
Forked from Forked from
= link_to project_path(forked_from_project) do = link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name) = forked_from_project.namespace.try(:name)
.cover-controls.left
.visibility-level-label.has_tooltip{title: project_visibility_level_description(@project.visibility_level), data: { container: 'body' } }
= visibility_level_icon(@project.visibility_level, fw: false)
= visibility_level_label(@project.visibility_level)
.cover-controls .cover-controls
- if current_user - if current_user
......
...@@ -32,14 +32,4 @@ ...@@ -32,14 +32,4 @@
= number_to_human_size(blob_size(blob)) = number_to_human_size(blob_size(blob))
.file-actions.hidden-xs .file-actions.hidden-xs
= render "actions" = render "actions"
- if blob.lfs_pointer? = render blob, blob: blob
= render "download", blob: blob
- elsif blob.text?
- if blob_svg?(blob)
= render "image", blob: blob
- else
= render "text", blob: blob
- elsif blob.image?
= render "image", blob: blob
- else
= render "download", blob: blob
.file-content.image_file .file-content.image_file
- if blob_svg?(blob) - if blob.svg?
- # We need to scrub SVG but we cannot do so in the RawController: it would - # We need to scrub SVG but we cannot do so in the RawController: it would
- # be wrong/strange if RawController modified the data. - # be wrong/strange if RawController modified the data.
- blob.load_all_data!(@repository) - blob.load_all_data!(@repository)
......
...@@ -76,10 +76,16 @@ ...@@ -76,10 +76,16 @@
= link_to '#down-build-trace', class: 'btn' do = link_to '#down-build-trace', class: 'btn' do
%i.fa.fa-angle-down %i.fa.fa-angle-down
- if @build.erased?
.erased.alert.alert-warning
- erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
- else
%pre.trace#build-trace %pre.trace#build-trace
%code.bash %code.bash
= preserve do = preserve do
= raw @build.trace_html = raw @build.trace_html
%div#down-build-trace %div#down-build-trace
.col-md-3 .col-md-3
...@@ -94,20 +100,34 @@ ...@@ -94,20 +100,34 @@
%h4.title Build artifacts %h4.title Build artifacts
.center .center
.btn-group{ role: :group } .btn-group{ role: :group }
= link_to "Download", @build.artifacts_download_url, class: 'btn btn-sm btn-primary' = link_to @build.artifacts_download_url, class: 'btn btn-sm btn-primary' do
= icon('download')
Download
- if @build.artifacts_metadata? - if @build.artifacts_metadata?
= link_to "Browse", @build.artifacts_browse_url, class: 'btn btn-sm btn-primary' = link_to @build.artifacts_browse_url, class: 'btn btn-sm btn-primary' do
= icon('folder-open')
Browse
.build-widget .build-widget
%h4.title %h4.title
Build ##{@build.id} Build ##{@build.id}
- if can?(current_user, :update_build, @project) - if can?(current_user, :update_build, @project)
.pull-right .center
.btn-group{ role: :group }
- if @build.cancel_url - if @build.cancel_url
= link_to "Cancel", @build.cancel_url, class: 'btn btn-sm btn-danger', method: :post = link_to "Cancel", @build.cancel_url, class: 'btn btn-sm btn-danger', method: :post
- elsif @build.retry_url - elsif @build.retry_url
= link_to "Retry", @build.retry_url, class: 'btn btn-sm btn-primary', method: :post = link_to "Retry", @build.retry_url, class: 'btn btn-sm btn-primary', method: :post
- if @build.erasable?
= link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
class: 'btn btn-sm btn-warning', method: :post,
data: { confirm: 'Are you sure you want to erase this build?' } do
= icon('eraser')
Erase
.clearfix
- if @build.duration - if @build.duration
%p %p
%span.attr-name Duration: %span.attr-name Duration:
...@@ -119,6 +139,10 @@ ...@@ -119,6 +139,10 @@
%p %p
%span.attr-name Finished: %span.attr-name Finished:
#{time_ago_with_tooltip(@build.finished_at)} #{time_ago_with_tooltip(@build.finished_at)}
- if @build.erased_at
%p
%span.attr-name Erased:
#{time_ago_with_tooltip(@build.erased_at)}
%p %p
%span.attr-name Runner: %span.attr-name Runner:
- if @build.runner && current_user && current_user.admin - if @build.runner && current_user && current_user.admin
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do
= icon('files-o') = icon('files-o')
Browse Files Browse Files
- unless @commit.has_been_reverted?(current_user)
= revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
%div %div
%p %p
......
#modal-revert-commit.modal
.modal-dialog
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title== Revert this #{revert_commit_type(commit)}
.modal-body
= form_tag revert_namespace_project_commit_path(@project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
.form-group.branch
= label_tag 'target_branch', 'Revert in branch', class: 'control-label'
.col-sm-10
= select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch"
- if can?(current_user, :push_code, @project)
.js-create-merge-request-container
.checkbox
- nonce = SecureRandom.hex
= label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
Start a <strong>new merge request</strong> with these changes
- else
= hidden_field_tag 'create_merge_request', 1
.form-actions
= submit_tag "Revert", class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project)
.inline.prepend-left-10
= commit_in_fork_help
:javascript
new NewCommitForm($('.js-create-dir-form'))
...@@ -12,3 +12,5 @@ ...@@ -12,3 +12,5 @@
= render "projects/diffs/diffs", diffs: @diffs, project: @project, = render "projects/diffs/diffs", diffs: @diffs, project: @project,
diff_refs: @diff_refs diff_refs: @diff_refs
= render "projects/notes/notes_with_form" = render "projects/notes/notes_with_form"
- if can_collaborate_with_project?
= render "projects/commit/revert", commit: @commit, title: @commit.title
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
= render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text = render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text
- if last_line > 0 - if last_line > 0
= render "projects/diffs/match_line", {line: "", = render "projects/diffs/match_line", { line: "",
line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file} line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file }
- if diff_file.diff.blank? && diff_file.mode_changed? - if diff_file.diff.blank? && diff_file.mode_changed?
.file-mode-changed .file-mode-changed
......
...@@ -119,13 +119,13 @@ ...@@ -119,13 +119,13 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
%p Get recent application code using the following command: %p Get recent application code using the following command:
.radio .radio
= f.label :build_allow_git_fetch do = f.label :build_allow_git_fetch_false do
= f.radio_button :build_allow_git_fetch, 'false' = f.radio_button :build_allow_git_fetch, 'false'
%strong git clone %strong git clone
%br %br
%span.descr Slower but makes sure you have a clean dir before every build %span.descr Slower but makes sure you have a clean dir before every build
.radio .radio
= f.label :build_allow_git_fetch do = f.label :build_allow_git_fetch_true do
= f.radio_button :build_allow_git_fetch, 'true' = f.radio_button :build_allow_git_fetch, 'true'
%strong git fetch %strong git fetch
%br %br
......
...@@ -16,23 +16,15 @@ ...@@ -16,23 +16,15 @@
= link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name") = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
- upvotes, downvotes = issue.upvotes, issue.downvotes - upvotes, downvotes = issue.upvotes, issue.downvotes
- if upvotes > 0 || downvotes > 0 - if upvotes > 0
%li %li
= icon('thumbs-up') = icon('thumbs-up')
= upvotes = upvotes
- else
%li{ class: 'issue-no-votes' }
= icon('thumbs-up')
= upvotes
- if upvotes > 0 || downvotes > 0 - if downvotes > 0
%li %li
= icon('thumbs-down') = icon('thumbs-down')
= downvotes = downvotes
- else
%li{ class: 'issue-no-votes' }
= icon('thumbs-down')
= downvotes
- note_count = issue.notes.user.count - note_count = issue.notes.user.count
- if note_count > 0 - if note_count > 0
......
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true = f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true
.form-group
= f.label :description, class: 'control-label'
.col-sm-10
= f.text_field :description, class: "form-control js-quick-submit"
.form-group .form-group
= f.label :color, "Background color", class: 'control-label' = f.label :color, "Background color", class: 'control-label'
.col-sm-10 .col-sm-10
......
%li{id: dom_id(label)} %li{id: dom_id(label)}
= link_to_label(label) = render "shared/label_row", label: label
.pull-right .pull-right
%strong.append-right-20 %strong.append-right-20
= link_to_label(label) do = link_to_label(label) do
......
...@@ -25,23 +25,15 @@ ...@@ -25,23 +25,15 @@
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
- upvotes, downvotes = merge_request.upvotes, merge_request.downvotes - upvotes, downvotes = merge_request.upvotes, merge_request.downvotes
- if upvotes > 0 || downvotes > 0 - if upvotes > 0
%li %li
= icon('thumbs-up') = icon('thumbs-up')
= upvotes = upvotes
- else
%li{ class: 'merge-request-no-votes' }
= icon('thumbs-up')
= upvotes
- if upvotes > 0 || downvotes > 0 - if downvotes > 0
%li %li
= icon('thumbs-down') = icon('thumbs-down')
= downvotes = downvotes
- else
%li{ class: 'merge-request-no-votes' }
= icon('thumbs-down')
= downvotes
- note_count = merge_request.mr_and_commit_notes.user.count - note_count = merge_request.mr_and_commit_notes.user.count
- if note_count > 0 - if note_count > 0
......
...@@ -85,6 +85,8 @@ ...@@ -85,6 +85,8 @@
= spinner = spinner
= render 'shared/issuable/sidebar', issuable: @merge_request = render 'shared/issuable/sidebar', issuable: @merge_request
- if @merge_request.can_be_reverted?
= render "projects/commit/revert", commit: @merge_request.merge_commit, title: @merge_request.title
:javascript :javascript
var merge_request; var merge_request;
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
Merge Request ##{@merge_request.iid} Merge Request ##{@merge_request.iid}
%span.creator %span.creator
&middot; &middot;
opened by #{link_to_member(@project, @merge_request.author, size: 24)} by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot; &middot;
= time_ago_with_tooltip(@merge_request.created_at) = time_ago_with_tooltip(@merge_request.created_at)
......
...@@ -8,20 +8,18 @@ ...@@ -8,20 +8,18 @@
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)} #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
%div %div
- if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true') - if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true')
%p
The changes were merged into The changes were merged into
#{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
The source branch has been removed. The source branch has been removed.
= render 'projects/merge_requests/widget/merged_buttons'
- elsif @merge_request.can_remove_source_branch?(current_user) - elsif @merge_request.can_remove_source_branch?(current_user)
.remove_source_branch_widget .remove_source_branch_widget
%p %p
The changes were merged into The changes were merged into
#{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
You can remove the source branch now. You can remove the source branch now.
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do = render 'projects/merge_requests/widget/merged_buttons', source_branch_exists: true
%i.fa.fa-times
Remove Source Branch
.remove_source_branch_widget.failed.hide .remove_source_branch_widget.failed.hide
%p %p
Failed to remove source branch '#{@merge_request.source_branch}'. Failed to remove source branch '#{@merge_request.source_branch}'.
......
- source_branch_exists = local_assigns.fetch(:source_branch_exists, false)
- mr_can_be_reverted = @merge_request.can_be_reverted?
- if source_branch_exists || mr_can_be_reverted
.btn-group
- if source_branch_exists
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-grouped btn-sm remove_source_branch" do
= icon('trash-o')
Remove Source Branch
- if mr_can_be_reverted
= revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) } %li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
%span %span
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.cgray ##{issue.iid}
= link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
.issue-detail
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.issue-number ##{issue.iid}
- issue.labels.each do |label|
= render_colored_label(label)
- if issue.assignee
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s24", alt: ''
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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