mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-13 22:59:19 +10:00
Merge branch 'master' into avatar-cropping
This commit is contained in:
+33
-33
@@ -1,4 +1,4 @@
|
||||
image: "ruby:2.2"
|
||||
image: "ruby:2.1"
|
||||
|
||||
services:
|
||||
- mysql:latest
|
||||
@@ -6,7 +6,7 @@ services:
|
||||
- redis:latest
|
||||
|
||||
cache:
|
||||
key: "ruby22"
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
|
||||
@@ -140,87 +140,87 @@ bundler:audit:
|
||||
- mysql
|
||||
allow_failure: true
|
||||
|
||||
# Ruby 2.1 jobs
|
||||
# Ruby 2.2 jobs
|
||||
|
||||
spec:feature:ruby21:
|
||||
image: ruby:2.1
|
||||
spec:feature:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:api:ruby21:
|
||||
image: ruby:2.1
|
||||
spec:api:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:models:ruby21:
|
||||
image: ruby:2.1
|
||||
spec:models:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:lib:ruby21:
|
||||
image: ruby:2.1
|
||||
spec:lib:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:services:ruby21:
|
||||
image: ruby:2.1
|
||||
spec:services:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:benchmark:ruby21:
|
||||
image: ruby:2.1
|
||||
spec:benchmark:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake spec:benchmark
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
@@ -228,56 +228,56 @@ spec:benchmark:ruby21:
|
||||
- mysql
|
||||
allow_failure: true
|
||||
|
||||
spec:other:ruby21:
|
||||
image: ruby:2.1
|
||||
spec:other:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:half:ruby21:
|
||||
image: ruby:2.1
|
||||
spinach:project:half:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:rest:ruby21:
|
||||
image: ruby:2.1
|
||||
spinach:project:rest:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:other:ruby21:
|
||||
image: ruby:2.1
|
||||
spinach:other:ruby22:
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
|
||||
cache:
|
||||
key: "ruby21"
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.2.4
|
||||
2.1.8
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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)
|
||||
- Show Crowd login tab when sign in is disabled and Crowd is enabled (Peter Hudec)
|
||||
|
||||
v 8.5.0
|
||||
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
|
||||
- Cache various Repository methods to improve performance (Yorick Peterse)
|
||||
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
|
||||
@@ -46,22 +49,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/...". Use "merge_requests/:merge_request_id/..." instead
|
||||
- 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
|
||||
- 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)
|
||||
- Improve UI consistency between projects and groups lists
|
||||
- Add sort dropdown to dashboard projects page
|
||||
- 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)
|
||||
- In seach autocomplete show only groups and projects you are member of
|
||||
- Don't process cross-reference notes from forks
|
||||
- Fix: init.d script not working on OS X
|
||||
- Faster snippet search
|
||||
- Added API to download build artifacts
|
||||
- Title for milestones should be unique (Zeger-Jan van de Weg)
|
||||
- Validate correctness of maximum attachment size application setting
|
||||
- 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 broken link to project in build notification emails
|
||||
- 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
|
||||
- Update omniauth-saml gem to 1.4.2
|
||||
|
||||
+13
-13
@@ -34,7 +34,7 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial
|
||||
edition. Throughout this guide you will see references to CE and EE for
|
||||
abbreviation.
|
||||
|
||||
If you have read this guide and want to know how the GitLab [core-team][]
|
||||
If you have read this guide and want to know how the GitLab [core team][core-team]
|
||||
operates please see [the GitLab contributing process](PROCESS.md).
|
||||
|
||||
## Contributor license agreement
|
||||
@@ -68,10 +68,10 @@ for audiences of all ages.
|
||||
## Helping others
|
||||
|
||||
Please help other GitLab users when you can. The channels people will reach out
|
||||
on can be found on the [getting help page][].
|
||||
on can be found on the [getting help page][getting-help].
|
||||
|
||||
Sign up for the mailing list, answer GitLab questions on StackOverflow or
|
||||
respond in the IRC channel. You can also sign up on [CodeTriage][] to help with
|
||||
respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
|
||||
the remaining issues on the GitHub issue tracker.
|
||||
|
||||
## I want to contribute!
|
||||
@@ -115,7 +115,7 @@ For feature proposals for EE, open an issue on the
|
||||
|
||||
In order to help track the feature proposals, we have created a
|
||||
[`feature proposal`][fpl] label. For the time being, users that are not members
|
||||
of the project cannot add labels. You can instead ask one of the [core team][]
|
||||
of the project cannot add labels. You can instead ask one of the [core team][core-team]
|
||||
members to add the label `feature proposal` to the issue.
|
||||
|
||||
Please keep feature proposals as small and simple as possible, complex ones
|
||||
@@ -299,8 +299,8 @@ to us than having a minimal commit log. The smaller an MR is the more likely it
|
||||
is it will be merged (quickly). After that you can send more MRs to enhance it.
|
||||
|
||||
For examples of feedback on merge requests please look at already
|
||||
[closed merge requests][]. If you would like quick feedback on your merge
|
||||
request feel free to mention one of the Merge Marshalls of the [core team][].
|
||||
[closed merge requests][closed-merge-requests]. If you would like quick feedback on your merge
|
||||
request feel free to mention one of the Merge Marshalls of the [core team][core-team].
|
||||
Please ensure that your merge request meets the contribution acceptance criteria.
|
||||
|
||||
When having your code reviewed and when reviewing merge requests please take the
|
||||
@@ -369,7 +369,7 @@ Like all merge requests the target should be master so all bugfixes are in maste
|
||||
## Definition of done
|
||||
|
||||
If you contribute to GitLab please know that changes involve more than just
|
||||
code. We have the following [definition of done][]. Please ensure you support
|
||||
code. We have the following [definition of done][definition-of-done]. Please ensure you support
|
||||
the feature you contribute through all of these steps.
|
||||
|
||||
1. Description explaining the relevancy (see following item)
|
||||
@@ -448,12 +448,12 @@ when an individual is representing the project or its community.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior can be
|
||||
reported by emailing `contact@gitlab.com`.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][], version 1.1.0,
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
|
||||
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
|
||||
|
||||
[core-team]: https://about.gitlab.com/core-team/
|
||||
[getting help page]: https://about.gitlab.com/getting-help/
|
||||
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[getting-help]: https://about.gitlab.com/getting-help/
|
||||
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
|
||||
[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
|
||||
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
|
||||
@@ -467,9 +467,9 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
||||
[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls
|
||||
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
|
||||
[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
|
||||
[closed merge requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
|
||||
[definition of done]: http://guide.agilealliance.org/guide/definition-of-done.html
|
||||
[Contributor Covenant]: http://contributor-covenant.org
|
||||
[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
|
||||
[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
|
||||
[contributor-covenant]: http://contributor-covenant.org
|
||||
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
|
||||
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
|
||||
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.4
|
||||
0.6.5
|
||||
|
||||
@@ -50,7 +50,7 @@ gem "browser", '~> 1.0.0'
|
||||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem "gitlab_git", '~> 8.1'
|
||||
gem "gitlab_git", '~> 8.2'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
@@ -115,7 +115,7 @@ gem 'diffy', '~> 3.0.3'
|
||||
|
||||
# Application server
|
||||
group :unicorn do
|
||||
gem "unicorn", '~> 4.8.2'
|
||||
gem "unicorn", '~> 4.9.0'
|
||||
gem 'unicorn-worker-killer', '~> 0.4.2'
|
||||
end
|
||||
|
||||
@@ -207,7 +207,7 @@ gem 'jquery-turbolinks', '~> 2.1.0'
|
||||
gem 'addressable', '~> 2.3.8'
|
||||
gem 'bootstrap-sass', '~> 3.3.0'
|
||||
gem 'font-awesome-rails', '~> 4.2'
|
||||
gem 'gitlab_emoji', '~> 0.2.0'
|
||||
gem 'gitlab_emoji', '~> 0.3.0'
|
||||
gem 'gon', '~> 6.0.1'
|
||||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||
gem 'jquery-rails', '~> 4.0.0'
|
||||
@@ -221,7 +221,7 @@ gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven'
|
||||
gem 'sentry-raven', '~> 0.15'
|
||||
|
||||
# Metrics
|
||||
group :metrics do
|
||||
|
||||
+14
-14
@@ -50,7 +50,7 @@ GEM
|
||||
after_commit_queue (1.3.0)
|
||||
activerecord (>= 3.0)
|
||||
akismet (2.0.0)
|
||||
allocations (1.0.3)
|
||||
allocations (1.0.4)
|
||||
annotate (2.6.10)
|
||||
activerecord (>= 3.2, <= 4.3)
|
||||
rake (~> 10.4)
|
||||
@@ -336,11 +336,11 @@ GEM
|
||||
ruby-progressbar (~> 1.4)
|
||||
gemnasium-gitlab-service (0.2.6)
|
||||
rugged (~> 0.21)
|
||||
gemojione (2.1.1)
|
||||
gemojione (2.2.1)
|
||||
json
|
||||
get_process_mem (0.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
github-linguist (4.7.3)
|
||||
github-linguist (4.7.5)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
escape_utils (~> 1.1.0)
|
||||
mime-types (>= 1.19)
|
||||
@@ -355,13 +355,13 @@ GEM
|
||||
diff-lcs (~> 1.1)
|
||||
mime-types (~> 1.15)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_emoji (0.2.0)
|
||||
gemojione (~> 2.1)
|
||||
gitlab_git (8.1.0)
|
||||
gitlab_emoji (0.3.1)
|
||||
gemojione (~> 2.2, >= 2.2.1)
|
||||
gitlab_git (8.2.0)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
rugged (~> 0.23.3)
|
||||
rugged (~> 0.24.0b13)
|
||||
gitlab_meta (7.0)
|
||||
gitlab_omniauth-ldap (1.2.1)
|
||||
net-ldap (~> 0.9)
|
||||
@@ -701,7 +701,7 @@ GEM
|
||||
rubyntlm (0.5.2)
|
||||
rubypants (0.2.0)
|
||||
rufus-scheduler (3.1.10)
|
||||
rugged (0.23.3)
|
||||
rugged (0.24.0b13)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (2.1.0)
|
||||
nokogiri (>= 1.4.4)
|
||||
@@ -723,7 +723,7 @@ GEM
|
||||
activesupport (>= 3.1, < 4.3)
|
||||
select2-rails (3.5.9.3)
|
||||
thor (~> 0.14)
|
||||
sentry-raven (0.15.4)
|
||||
sentry-raven (0.15.6)
|
||||
faraday (>= 0.7.6)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.6.0)
|
||||
@@ -834,7 +834,7 @@ GEM
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.1)
|
||||
unicorn (4.8.3)
|
||||
unicorn (4.9.0)
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
raindrops (~> 0.7)
|
||||
@@ -932,8 +932,8 @@ DEPENDENCIES
|
||||
github-linguist (~> 4.7.0)
|
||||
github-markup (~> 1.3.1)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_emoji (~> 0.2.0)
|
||||
gitlab_git (~> 8.1)
|
||||
gitlab_emoji (~> 0.3.0)
|
||||
gitlab_git (~> 8.2)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.1.0)
|
||||
@@ -1009,7 +1009,7 @@ DEPENDENCIES
|
||||
sdoc (~> 0.3.20)
|
||||
seed-fu (~> 2.3.5)
|
||||
select2-rails (~> 3.5.9)
|
||||
sentry-raven
|
||||
sentry-raven (~> 0.15)
|
||||
settingslogic (~> 2.0.9)
|
||||
sham_rack
|
||||
shoulda-matchers (~> 2.8.0)
|
||||
@@ -1036,7 +1036,7 @@ DEPENDENCIES
|
||||
uglifier (~> 2.7.2)
|
||||
underscore-rails (~> 1.8.0)
|
||||
unf (~> 0.1.4)
|
||||
unicorn (~> 4.8.2)
|
||||
unicorn (~> 4.9.0)
|
||||
unicorn-worker-killer (~> 0.4.2)
|
||||
version_sorter (~> 2.0.0)
|
||||
virtus (~> 1.0.1)
|
||||
|
||||
@@ -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:
|
||||
|
||||
- Ubuntu/Debian/CentOS/RHEL
|
||||
- Ruby (MRI) 2.1 or 2.2
|
||||
- Ruby (MRI) 2.1
|
||||
- Git 1.7.10+
|
||||
- Redis 2.8+
|
||||
- MySQL or PostgreSQL
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 813 KiB After Width: | Height: | Size: 257 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 674 KiB |
@@ -76,6 +76,8 @@ class Dispatcher
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:show'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
|
||||
new TreeView() if $('#tree-slider').length
|
||||
when 'groups:show'
|
||||
new Activities()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
@@ -91,7 +93,7 @@ class Dispatcher
|
||||
new TreeView()
|
||||
when 'projects:find_file:show'
|
||||
shortcut_handler = true
|
||||
when 'projects:blob:show'
|
||||
when 'projects:blob:show', 'projects:blame:show'
|
||||
new LineHighlighter()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:labels:new', 'projects:labels:edit'
|
||||
|
||||
@@ -15,3 +15,5 @@ class @IssuableContext
|
||||
block.find('.selectbox').show()
|
||||
block.find('.value').hide()
|
||||
block.find('.js-select2').select2("open")
|
||||
|
||||
$(".right-sidebar").niceScroll()
|
||||
|
||||
@@ -146,6 +146,7 @@ class @MergeRequestTabs
|
||||
success: (data) =>
|
||||
document.querySelector("div#diffs").innerHTML = data.html
|
||||
$('div#diffs .js-syntax-highlight').syntaxHighlight()
|
||||
@expandViewContainer() if @diffViewType() is 'parallel'
|
||||
@diffsLoaded = true
|
||||
@scrollToElement("#diffs")
|
||||
|
||||
@@ -177,3 +178,10 @@ class @MergeRequestTabs
|
||||
options = $.extend({}, defaults, options)
|
||||
|
||||
$.ajax(options)
|
||||
|
||||
# Returns diff view type
|
||||
diffViewType: ->
|
||||
$('.inline-parallel-buttons a.active').data('view-type')
|
||||
|
||||
expandViewContainer: ->
|
||||
$('.container-fluid').removeClass('container-limited')
|
||||
|
||||
@@ -62,14 +62,24 @@ class @Milestone
|
||||
dataType: "json"
|
||||
|
||||
constructor: ->
|
||||
oldMouseStart = $.ui.sortable.prototype._mouseStart
|
||||
$.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
|
||||
this._trigger "beforeStart", event, this._uiHash()
|
||||
oldMouseStart.apply this, [event, overrideHandle, noActivation]
|
||||
|
||||
@bindIssuesSorting()
|
||||
@bindMergeRequestSorting()
|
||||
@bindTabsSwitching
|
||||
|
||||
bindIssuesSorting: ->
|
||||
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
|
||||
connectWith: ".issues-sortable-list",
|
||||
dropOnEmpty: true,
|
||||
items: "li:not(.ui-sort-disabled)",
|
||||
beforeStart: (event, ui) ->
|
||||
$(".issues-sortable-list").css "min-height", ui.item.outerHeight()
|
||||
stop: (event, ui) ->
|
||||
$(".issues-sortable-list").css "min-height", "0px"
|
||||
update: (event, ui) ->
|
||||
data = $(this).sortable("serialize")
|
||||
Milestone.sortIssues(data)
|
||||
@@ -95,10 +105,22 @@ class @Milestone
|
||||
).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()
|
||||
|
||||
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
|
||||
connectWith: ".merge_requests-sortable-list",
|
||||
dropOnEmpty: true,
|
||||
items: "li:not(.ui-sort-disabled)",
|
||||
beforeStart: (event, ui) ->
|
||||
$(".merge_requests-sortable-list").css "min-height", ui.item.outerHeight()
|
||||
stop: (event, ui) ->
|
||||
$(".merge_requests-sortable-list").css "min-height", "0px"
|
||||
update: (event, ui) ->
|
||||
data = $(this).sortable("serialize")
|
||||
Milestone.sortMergeRequests(data)
|
||||
|
||||
@@ -24,6 +24,10 @@ class @ShortcutsIssuable extends ShortcutsNavigation
|
||||
@nextIssue()
|
||||
return false
|
||||
)
|
||||
Mousetrap.bind('e', =>
|
||||
@editIssue()
|
||||
return false
|
||||
)
|
||||
|
||||
|
||||
if isMergeRequest
|
||||
@@ -63,3 +67,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
|
||||
|
||||
# Focus the input field
|
||||
replyField.focus()
|
||||
|
||||
editIssue: ->
|
||||
$editBtn = $('.issuable-edit')
|
||||
Turbolinks.visit($editBtn.attr('href'))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
class @Wikis
|
||||
constructor: ->
|
||||
$('.build-new-wiki').bind 'click', (e) =>
|
||||
$('.new-wiki-page').on 'submit', (e) =>
|
||||
$('[data-error~=slug]').addClass('hidden')
|
||||
field = $('#new_wiki_path')
|
||||
slug = @slugify(field.val())
|
||||
@@ -10,6 +10,7 @@ class @Wikis
|
||||
if (slug.length > 0)
|
||||
path = field.attr('data-wikis-path')
|
||||
location.href = path + '/' + slug
|
||||
e.preventDefault()
|
||||
|
||||
dasherize: (value) ->
|
||||
value.replace(/[_\s]+/g, '-')
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
|
||||
@include box-shadow($gl-btn-active-background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
|
||||
&:active {
|
||||
@include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12));
|
||||
@include box-shadow ($gl-btn-active-background);
|
||||
|
||||
background-color: $dark;
|
||||
border-color: $border-dark;
|
||||
@@ -68,6 +68,12 @@
|
||||
@include btn-default;
|
||||
@include btn-white;
|
||||
|
||||
color: $gl-text-color;
|
||||
|
||||
&:focus:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&.btn-small,
|
||||
&.btn-sm {
|
||||
padding: 4px 10px;
|
||||
@@ -130,6 +136,11 @@
|
||||
&.disabled {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
.caret {
|
||||
margin-left: 5px;
|
||||
color: $gray-darkest;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
@@ -179,7 +190,7 @@
|
||||
}
|
||||
|
||||
.active {
|
||||
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
|
||||
@include box-shadow($gl-btn-active-background);
|
||||
|
||||
border: 1px solid #c6cacf !important;
|
||||
background-color: #e4e7ed !important;
|
||||
|
||||
@@ -56,6 +56,10 @@ hr {
|
||||
margin: $gl-padding 0;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
margin: 6px 0 0;
|
||||
}
|
||||
|
||||
.dropdown-menu > li > a {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
@@ -21,10 +21,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.issues-filters,
|
||||
.issues_bulk_update {
|
||||
.select2-container .select2-choice {
|
||||
color: #444 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,4 +117,4 @@ body {
|
||||
&.ui_violet {
|
||||
@include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,7 @@ header {
|
||||
line-height: $header-height;
|
||||
font-weight: normal;
|
||||
color: #4c4e54;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -44,8 +44,10 @@
|
||||
white-space: nowrap;
|
||||
|
||||
i {
|
||||
float: left;
|
||||
margin-top: 3px;
|
||||
margin-right: 5px;
|
||||
visibility: hidden;
|
||||
@extend .pull-left;
|
||||
}
|
||||
|
||||
&:hover i {
|
||||
|
||||
@@ -6,31 +6,28 @@
|
||||
|
||||
.status-box {
|
||||
@include border-radius(3px);
|
||||
|
||||
display: block;
|
||||
float: left;
|
||||
padding: 0 $gl-btn-padding;
|
||||
font-weight: normal;
|
||||
margin-top: 5px;
|
||||
margin-right: 10px;
|
||||
color: #FFF;
|
||||
font-size: $gl-font-size;
|
||||
line-height: 25px;
|
||||
|
||||
&.status-box-closed {
|
||||
background-color: $gl-danger;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
&.status-box-merged {
|
||||
background-color: $gl-primary;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
&.status-box-open {
|
||||
background-color: $green-light;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
&.status-box-expired {
|
||||
background: #cea61b;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,12 +77,21 @@
|
||||
margin-bottom: 0px;
|
||||
|
||||
> .dropdown {
|
||||
margin-right: 10px;
|
||||
margin-right: $gl-padding-top;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
> .btn {
|
||||
margin-right: $gl-padding-top;
|
||||
display: inline-block;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .btn-grouped {
|
||||
float: none;
|
||||
}
|
||||
|
||||
> form {
|
||||
@@ -94,7 +103,7 @@
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 10px;
|
||||
margin-right: $gl-padding-top;
|
||||
|
||||
/* Medium devices (desktops, 992px and up) */
|
||||
@media (min-width: $screen-md-min) { width: 200px; }
|
||||
|
||||
@@ -1,49 +1,53 @@
|
||||
/** Select2 selectbox style override **/
|
||||
.select2-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.select2-container, .select2-container.select2-drop-above {
|
||||
.select2-choice {
|
||||
background: #FFF;
|
||||
border-color: #DDD;
|
||||
height: 36px;
|
||||
padding: 6px $gl-padding;
|
||||
background: #fff;
|
||||
border-color: $input-border;
|
||||
border-color: $border-white-light;
|
||||
height: 35px;
|
||||
padding: $gl-vert-padding $gl-btn-padding;
|
||||
font-size: $gl-font-size;
|
||||
line-height: 1.42857143;
|
||||
|
||||
@include border-radius(2px);
|
||||
@include border-radius($border-radius-default);
|
||||
|
||||
.select2-arrow {
|
||||
background: #FFF;
|
||||
border-left: none;
|
||||
padding-top: 5px;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding-top: 6px;
|
||||
padding-right: 10px;
|
||||
|
||||
b {
|
||||
@extend .caret;
|
||||
color: $gray-darkest;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-chosen {
|
||||
color: $gl-text-color;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
&.select2-default {
|
||||
.select2-chosen {
|
||||
color: #999;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $gray-dark;
|
||||
border-color: $border-white-normal;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container .select2-choice, .select2-container.select2-drop-above .select2-choice{
|
||||
color: #7f8fa4;
|
||||
border: 1px solid #e7e9ed;
|
||||
}
|
||||
|
||||
|
||||
.select2-drop {
|
||||
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
|
||||
@include border-radius (0px);
|
||||
|
||||
padding: 16px;
|
||||
border: none !important;
|
||||
@include border-radius ($border-radius-default);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
padding: 9px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.select2-drop{
|
||||
@@ -56,15 +60,30 @@
|
||||
|
||||
.select2-results li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: 600;
|
||||
color: #313236;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
|
||||
.select2-container-active {
|
||||
.select2-choice, .select2-choices {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
}
|
||||
|
||||
.select2-dropdown-open {
|
||||
.select2-choice {
|
||||
border-color: $border-white-normal;
|
||||
outline: 0;
|
||||
background-image: none;
|
||||
background-color: $white-dark;
|
||||
@include box-shadow($gl-btn-active-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container-multi {
|
||||
.select2-choices {
|
||||
@include border-radius(2px);
|
||||
@include border-radius($border-radius-default);
|
||||
border-color: $input-border;
|
||||
background: white;
|
||||
padding-left: $gl-padding / 2;
|
||||
background: none;
|
||||
|
||||
.select2-search-field input {
|
||||
padding: $gl-padding / 2;
|
||||
@@ -76,14 +95,16 @@
|
||||
|
||||
.select2-search-choice {
|
||||
margin: 8px 0 0 8px;
|
||||
background: white;
|
||||
box-shadow: none;
|
||||
border-color: $input-border;
|
||||
color: $gl-text-color;
|
||||
line-height: 15px;
|
||||
background-color: $background-color;
|
||||
background-image: none;
|
||||
|
||||
.select2-search-choice-close {
|
||||
top: 5px;
|
||||
top: 4px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
&.select2-search-choice-focus {
|
||||
@@ -91,22 +112,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.select2-container-active .select2-choices,
|
||||
&.select2-dropdown-open .select2-choices {
|
||||
border-color: $border-white-normal;
|
||||
@include box-shadow($gl-btn-active-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
border: 1px solid #BBB !important;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
|
||||
&.select2-drop-above {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
background: #fafafa;
|
||||
border-color: #DDD;
|
||||
}
|
||||
|
||||
.select2-results {
|
||||
max-height: 350px;
|
||||
.select2-highlighted {
|
||||
@@ -115,8 +139,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
width: 100% !important;
|
||||
.select2-search {
|
||||
padding: 15px 15px 5px;
|
||||
|
||||
.select2-drop-auto-width & {
|
||||
padding: 15px 15px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
padding: 2px 25px 2px 5px;
|
||||
background: #fff image-url('select2.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0px bottom 6px;
|
||||
border: 1px solid $input-border;
|
||||
@include border-radius($border-radius-default);
|
||||
@include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s);
|
||||
|
||||
&:focus {
|
||||
border-color: $input-border-focus;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-search input.select2-active {
|
||||
background-color: #fff;
|
||||
background-image: image-url('select2-spinner.gif') !important;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 5px center !important;
|
||||
background-size: 16px 16px !important;
|
||||
}
|
||||
|
||||
/** Branch/tag selector **/
|
||||
@@ -124,10 +174,19 @@
|
||||
width: 160px !important;
|
||||
}
|
||||
|
||||
.ajax-users-dropdown, .ajax-project-users-dropdown {
|
||||
.select2-search {
|
||||
padding-top: 2px;
|
||||
}
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-ajax-error,
|
||||
.select2-results .select2-selection-limit {
|
||||
background: $gray-light;
|
||||
display: list-item;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
|
||||
.select2-results {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.ajax-users-select {
|
||||
|
||||
@@ -31,6 +31,7 @@ $gl-padding-top:10px;
|
||||
$gl-avatar-size: 40px;
|
||||
$secondary-text: #7f8fa4;
|
||||
$error-exclamation-point: #E62958;
|
||||
$border-radius-default: 3px;
|
||||
|
||||
/*
|
||||
* Color schema
|
||||
@@ -100,6 +101,8 @@ $gl-success: $green-normal;
|
||||
$gl-info: $blue-normal;
|
||||
$gl-warning: $orange-normal;
|
||||
$gl-danger: $red-normal;
|
||||
$gl-btn-active-background: rgba(0, 0, 0, 0.12);
|
||||
$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background;
|
||||
|
||||
/*
|
||||
* Commit Diff Colors
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
.identifier {
|
||||
color: #5c5d5e;
|
||||
}
|
||||
|
||||
.issue_created_ago, .author_link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.issue-meta {
|
||||
margin-left: 65px
|
||||
}
|
||||
}
|
||||
|
||||
.detail-page-description {
|
||||
|
||||
+1735
-1271
File diff suppressed because it is too large
Load Diff
@@ -64,7 +64,6 @@
|
||||
// This prevents the mess when resizing the sidebar
|
||||
// of elements repositioning themselves..
|
||||
width: $gutter_inner_width;
|
||||
overflow-x: hidden;
|
||||
// --
|
||||
|
||||
&:first-child {
|
||||
@@ -90,7 +89,6 @@
|
||||
|
||||
.gutter-toggle {
|
||||
margin-left: 20px;
|
||||
border-left: 1px solid $border-gray-light;
|
||||
padding-left: 10px;
|
||||
|
||||
&:hover {
|
||||
@@ -157,11 +155,10 @@
|
||||
.right-sidebar {
|
||||
position: fixed;
|
||||
top: 58px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
transition-duration: .3s;
|
||||
transition: width .3s;
|
||||
background: $gray-light;
|
||||
overflow: scroll;
|
||||
padding: 10px 20px;
|
||||
|
||||
&.right-sidebar-expanded {
|
||||
@@ -170,6 +167,14 @@
|
||||
hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gutter-toggle {
|
||||
border-left: 1px solid $border-gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.subscribe-button {
|
||||
@@ -181,7 +186,6 @@
|
||||
&.right-sidebar-collapsed {
|
||||
width: $sidebar_collapsed_width;
|
||||
padding-top: 0;
|
||||
overflow-x: hidden;
|
||||
|
||||
hr {
|
||||
margin: 0;
|
||||
@@ -192,21 +196,13 @@
|
||||
}
|
||||
|
||||
.block {
|
||||
border-bottom: none;
|
||||
width: $sidebar_collapsed_width - 1px;
|
||||
margin-left: -19px;
|
||||
padding: 15px 0 0 0;
|
||||
border-bottom: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: $gray-normal;
|
||||
border: 1px solid $border-gray-normal;
|
||||
&:hover {
|
||||
background: $gray-dark;
|
||||
border: 1px solid $border-gray-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&.right-sidebar-collapsed {
|
||||
.issuable-count,
|
||||
.issuable-nav,
|
||||
.assignee > *,
|
||||
@@ -219,15 +215,13 @@
|
||||
}
|
||||
|
||||
.gutter-toggle {
|
||||
margin-left: -$gutter_inner_width + 4;
|
||||
margin-left: -36px;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-icon {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 62px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-left: -19px;
|
||||
padding-bottom: 10px;
|
||||
color: #999999;
|
||||
|
||||
@@ -247,14 +241,15 @@
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.right-sidebar-expanded {
|
||||
.sidebar-collapsed-icon {
|
||||
display: none;
|
||||
.btn {
|
||||
background: $gray-normal;
|
||||
border: 1px solid $border-gray-normal;
|
||||
&:hover {
|
||||
background: $gray-dark;
|
||||
border: 1px solid $border-gray-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,4 +258,4 @@
|
||||
small {
|
||||
color: $gray-darkest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.manage-labels-list {
|
||||
.label-row {
|
||||
.label {
|
||||
padding: 9px;
|
||||
font-size: 14px;
|
||||
|
||||
@@ -236,4 +236,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,60 @@ li.milestone {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
.cover-controls {
|
||||
.project-settings-dropdown {
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,24 +74,19 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.visibility-icon {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
font-size: 18px;
|
||||
color: $gray;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0 $gl-padding;
|
||||
color: #5c5d5e;
|
||||
}
|
||||
}
|
||||
|
||||
.visibility-level-label {
|
||||
@extend .btn;
|
||||
@extend .btn-gray;
|
||||
|
||||
color: $gray;
|
||||
cursor: default;
|
||||
|
||||
i {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.project-repo-buttons {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 0px;
|
||||
@@ -191,10 +187,10 @@
|
||||
|
||||
.dropdown-menu {
|
||||
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
|
||||
@include border-radius (0px);
|
||||
@include border-radius ($border-radius-default);
|
||||
|
||||
border: none;
|
||||
padding: 16px 0;
|
||||
padding: 10px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 100;
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,3 @@
|
||||
margin-right: auto;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
.wiki-last-edit-by {
|
||||
font-size: 80%;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,6 @@ class Admin::LabelsController < Admin::ApplicationController
|
||||
end
|
||||
|
||||
def label_params
|
||||
params[:label].permit(:title, :color)
|
||||
params[:label].permit(:title, :description, :color)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
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 :repository
|
||||
helper_method :repository, :can_collaborate_with_project?
|
||||
|
||||
rescue_from Encoding::CompatibilityError do |exception|
|
||||
log_exception(exception)
|
||||
@@ -410,6 +410,13 @@ class ApplicationController < ActionController::Base
|
||||
current_user.nil? && root_path == request.path
|
||||
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
|
||||
|
||||
def set_default_sort
|
||||
|
||||
@@ -13,17 +13,11 @@ module CreatesCommit
|
||||
result = service.new(@tree_edit_project, current_user, commit_params).execute
|
||||
|
||||
if result[:status] == :success
|
||||
flash[:notice] = success_notice || "Your changes have been successfully committed."
|
||||
|
||||
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
|
||||
update_flash_notice(success_notice)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to success_path }
|
||||
format.json { render json: { message: "success", filePath: success_path } }
|
||||
format.html { redirect_to final_success_path(success_path) }
|
||||
format.json { render json: { message: "success", filePath: final_success_path(success_path) } }
|
||||
end
|
||||
else
|
||||
flash[:alert] = result[:message]
|
||||
@@ -41,14 +35,32 @@ module CreatesCommit
|
||||
end
|
||||
|
||||
def authorize_edit_tree!
|
||||
return if can?(current_user, :push_code, project)
|
||||
return if current_user && current_user.already_forked?(project)
|
||||
return if can_collaborate_with_project?
|
||||
|
||||
access_denied!
|
||||
end
|
||||
|
||||
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
|
||||
new_namespace_project_merge_request_path(
|
||||
@mr_source_project.namespace,
|
||||
@@ -62,6 +74,19 @@ module CreatesCommit
|
||||
)
|
||||
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?
|
||||
@mr_source_project != @mr_target_project
|
||||
end
|
||||
@@ -75,7 +100,7 @@ module CreatesCommit
|
||||
end
|
||||
|
||||
def set_commit_variables
|
||||
@mr_source_branch = @target_branch
|
||||
@mr_source_branch ||= @target_branch
|
||||
|
||||
if can?(current_user, :push_code, @project)
|
||||
# Edit file in this project
|
||||
@@ -89,7 +114,7 @@ module CreatesCommit
|
||||
else
|
||||
# Merge request to this project
|
||||
@mr_target_project = @project
|
||||
@mr_target_branch = @ref
|
||||
@mr_target_branch ||= @ref
|
||||
end
|
||||
else
|
||||
# Edit file in fork
|
||||
@@ -97,7 +122,7 @@ module CreatesCommit
|
||||
# Merge request from fork to this project
|
||||
@mr_source_project = @tree_edit_project
|
||||
@mr_target_project = @project
|
||||
@mr_target_branch = @ref
|
||||
@mr_target_branch ||= @ref
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,8 @@ module IssuesAction
|
||||
@issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE)
|
||||
@issues = @issues.preload(:author, :project)
|
||||
|
||||
@label = @issuable_finder.labels.first
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.atom { render layout: false }
|
||||
|
||||
@@ -5,5 +5,7 @@ module MergeRequestsAction
|
||||
@merge_requests = get_merge_requests_collection
|
||||
@merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE)
|
||||
@merge_requests = @merge_requests.preload(:author, :target_project)
|
||||
|
||||
@label = @issuable_finder.labels.first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
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
|
||||
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
|
||||
@provider = params[:provider]
|
||||
@error = params[:error]
|
||||
@@ -65,25 +85,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
log_audit_event(current_user, with: oauth['provider'])
|
||||
redirect_to profile_account_path, notice: 'Authentication method updated'
|
||||
else
|
||||
@user = Gitlab::OAuth::User.new(oauth)
|
||||
@user.save
|
||||
oauth_user = Gitlab::OAuth::User.new(oauth)
|
||||
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
|
||||
end
|
||||
continue_login_process
|
||||
end
|
||||
rescue Gitlab::OAuth::SignupDisabledError
|
||||
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
|
||||
@@ -104,6 +110,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
session[:service_tickets][provider] = ticket
|
||||
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
|
||||
@oauth ||= request.env['omniauth.auth']
|
||||
end
|
||||
|
||||
@@ -87,7 +87,7 @@ class Projects::BlobController < Projects::ApplicationController
|
||||
private
|
||||
|
||||
def blob
|
||||
@blob ||= @repository.blob_at(@commit.id, @path)
|
||||
@blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
|
||||
|
||||
if @blob
|
||||
@blob
|
||||
|
||||
@@ -56,6 +56,12 @@ class Projects::BuildsController < Projects::ApplicationController
|
||||
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
|
||||
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
|
||||
|
||||
def build
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#
|
||||
# Not to be confused with CommitsController, plural.
|
||||
class Projects::CommitController < Projects::ApplicationController
|
||||
include CreatesCommit
|
||||
|
||||
# Authorize
|
||||
before_action :require_non_empty_project
|
||||
before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
|
||||
@@ -9,6 +11,7 @@ class Projects::CommitController < Projects::ApplicationController
|
||||
before_action :authorize_read_commit_status!, only: [:builds]
|
||||
before_action :commit
|
||||
before_action :define_show_vars, only: [:show, :builds]
|
||||
before_action :authorize_edit_tree!, only: [:revert]
|
||||
|
||||
def show
|
||||
apply_diff_view_cookie!
|
||||
@@ -55,8 +58,37 @@ class Projects::CommitController < Projects::ApplicationController
|
||||
render layout: false
|
||||
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
|
||||
|
||||
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
|
||||
@commit ||= @project.commit(params[:id])
|
||||
end
|
||||
@@ -79,4 +111,16 @@ class Projects::CommitController < Projects::ApplicationController
|
||||
|
||||
@statuses = ci_commit.statuses if ci_commit
|
||||
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
|
||||
|
||||
@@ -32,7 +32,7 @@ class Projects::ForksController < Projects::ApplicationController
|
||||
if continue_params
|
||||
redirect_to continue_params[:to], notice: continue_params[:notice]
|
||||
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
|
||||
else
|
||||
|
||||
@@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController
|
||||
before_action :authorize_admin_project!
|
||||
before_action :require_no_repo, only: [:new, :create]
|
||||
before_action :redirect_if_progress, only: [:new, :create]
|
||||
before_action :redirect_if_no_import, only: :show
|
||||
|
||||
def new
|
||||
end
|
||||
@@ -63,14 +64,19 @@ class Projects::ImportsController < Projects::ApplicationController
|
||||
|
||||
def require_no_repo
|
||||
if @project.repository_exists?
|
||||
redirect_to(namespace_project_path(@project.namespace, @project))
|
||||
redirect_to namespace_project_path(@project.namespace, @project)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_if_progress
|
||||
if @project.import_in_progress?
|
||||
redirect_to namespace_project_import_path(@project.namespace, @project) &&
|
||||
return
|
||||
redirect_to namespace_project_import_path(@project.namespace, @project)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_if_no_import
|
||||
if @project.repository_exists? && @project.no_import?
|
||||
redirect_to namespace_project_path(@project.namespace, @project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
@issues = @issues.page(params[:page]).per(PER_PAGE)
|
||||
@label = @project.labels.find_by(title: params[:label_name])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
||||
@@ -69,7 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def label_params
|
||||
params.require(:label).permit(:title, :color)
|
||||
params.require(:label).permit(:title, :description, :color)
|
||||
end
|
||||
|
||||
def label
|
||||
|
||||
@@ -34,6 +34,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
|
||||
@merge_requests = @merge_requests.preload(:target_project)
|
||||
|
||||
@label = @project.labels.find_by(title: params[:label_name])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
@@ -179,6 +181,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
TodoService.new.merge_merge_request(merge_request, current_user)
|
||||
|
||||
@merge_request.update(merge_error: nil)
|
||||
|
||||
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
|
||||
@issues = @milestone.issues
|
||||
@users = @milestone.participants.uniq
|
||||
@merge_requests = @milestone.merge_requests
|
||||
@labels = @milestone.labels
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -64,9 +64,9 @@ class Projects::RefsController < Projects::ApplicationController
|
||||
}
|
||||
end
|
||||
|
||||
if @logs.present?
|
||||
@log_url = namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))
|
||||
@more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))
|
||||
offset = (@offset + @limit)
|
||||
if contents.size > offset
|
||||
@more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
||||
@@ -11,7 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def archive
|
||||
render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
|
||||
RepositoryArchiveCacheWorker.perform_async
|
||||
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
|
||||
head :ok
|
||||
rescue => ex
|
||||
logger.error("#{self.class.name}: #{ex}")
|
||||
return git_not_found!
|
||||
|
||||
@@ -236,7 +236,7 @@ class ProjectsController < ApplicationController
|
||||
Emoji.emojis.map do |name, emoji|
|
||||
{
|
||||
name: name,
|
||||
path: view_context.image_url("emoji/#{emoji["unicode"]}.png")
|
||||
path: view_context.image_url("#{emoji["unicode"]}.png")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -119,6 +119,20 @@ class IssuableFinder
|
||||
labels? && params[:label_name] == Label::None.title
|
||||
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?
|
||||
params[:assignee_id].present?
|
||||
end
|
||||
@@ -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").
|
||||
where(label_links: { id: nil })
|
||||
else
|
||||
label_names = params[:label_name].split(",")
|
||||
|
||||
items = items.joins(:labels).where(labels: { title: label_names })
|
||||
|
||||
if projects
|
||||
@@ -266,6 +278,10 @@ class IssuableFinder
|
||||
items
|
||||
end
|
||||
|
||||
def label_names
|
||||
params[:label_name].split(',')
|
||||
end
|
||||
|
||||
def current_user_related?
|
||||
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
# 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
|
||||
grouped_options_for_select(options, @ref || @project.default_branch)
|
||||
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
|
||||
# with suggestion to create MR
|
||||
def show_last_push_widget?(event)
|
||||
|
||||
@@ -127,10 +127,6 @@ module BlobHelper
|
||||
end
|
||||
end
|
||||
|
||||
def blob_svg?(blob)
|
||||
blob.language && blob.language.name == 'SVG'
|
||||
end
|
||||
|
||||
# SVGs can contain malicious JavaScript; only include whitelisted
|
||||
# elements and attributes. Note that this whitelist is by no means complete
|
||||
# and may omit some elements.
|
||||
|
||||
@@ -123,6 +123,37 @@ module CommitsHelper
|
||||
)
|
||||
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', 'data-container' => 'body', 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', 'data-container' => 'body', title: tooltip
|
||||
end
|
||||
end
|
||||
|
||||
def revert_commit_type(commit)
|
||||
if commit.merged_merge_request
|
||||
'merge request'
|
||||
else
|
||||
'commit'
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Private: Returns a link to a person. If the person has a matching user and
|
||||
|
||||
@@ -69,7 +69,7 @@ module DiffHelper
|
||||
end
|
||||
|
||||
def line_comments
|
||||
@line_comments ||= @line_notes.select(&:active?).group_by(&:line_code)
|
||||
@line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
|
||||
end
|
||||
|
||||
def organize_comments(type_left, type_right, line_code_left, line_code_right)
|
||||
@@ -137,7 +137,7 @@ module DiffHelper
|
||||
# Always use HTML to handle case where JSON diff rendered this button
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,6 +23,7 @@ module NavHelper
|
||||
if current_path?('merge_requests#show') ||
|
||||
current_path?('merge_requests#diffs') ||
|
||||
current_path?('merge_requests#commits') ||
|
||||
current_path?('merge_requests#builds') ||
|
||||
current_path?('issues#show')
|
||||
if cookies[:collapsed_gutter] == 'true'
|
||||
"page-gutter right-sidebar-collapsed"
|
||||
|
||||
@@ -11,6 +11,8 @@ module SortingHelper
|
||||
sort_value_largest_repo => sort_title_largest_repo,
|
||||
sort_value_recently_signin => sort_title_recently_signin,
|
||||
sort_value_oldest_signin => sort_title_oldest_signin,
|
||||
sort_value_downvotes => sort_title_downvotes,
|
||||
sort_value_upvotes => sort_title_upvotes
|
||||
}
|
||||
end
|
||||
|
||||
@@ -54,6 +56,14 @@ module SortingHelper
|
||||
'Oldest sign in'
|
||||
end
|
||||
|
||||
def sort_title_downvotes
|
||||
'Least popular'
|
||||
end
|
||||
|
||||
def sort_title_upvotes
|
||||
'Most popular'
|
||||
end
|
||||
|
||||
def sort_value_oldest_updated
|
||||
'updated_asc'
|
||||
end
|
||||
@@ -93,4 +103,12 @@ module SortingHelper
|
||||
def sort_value_oldest_signin
|
||||
'oldest_sign_in'
|
||||
end
|
||||
|
||||
def sort_value_downvotes
|
||||
'downvotes_desc'
|
||||
end
|
||||
|
||||
def sort_value_upvotes
|
||||
'upvotes_desc'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
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
|
||||
|
||||
return false unless on_top_of_branch?(project, ref)
|
||||
|
||||
can?(current_user, :push_code, project) ||
|
||||
(current_user && current_user.already_forked?(project))
|
||||
can_collaborate_with_project?(project)
|
||||
end
|
||||
|
||||
def tree_edit_branch(project = @project, ref = @ref)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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
|
||||
+44
-8
@@ -31,15 +31,19 @@
|
||||
# artifacts_file :text
|
||||
# gl_project_id :integer
|
||||
# artifacts_metadata :text
|
||||
# erased_by_id :integer
|
||||
# erased_at :datetime
|
||||
#
|
||||
|
||||
module Ci
|
||||
class Build < CommitStatus
|
||||
include Gitlab::Application.routes.url_helpers
|
||||
|
||||
LAZY_ATTRIBUTES = ['trace']
|
||||
|
||||
belongs_to :runner, class_name: 'Ci::Runner'
|
||||
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
|
||||
belongs_to :erased_by, class_name: 'User'
|
||||
|
||||
serialize :options
|
||||
|
||||
@@ -103,23 +107,22 @@ module Ci
|
||||
end
|
||||
|
||||
state_machine :status, initial: :pending do
|
||||
after_transition pending: :running do |build, transition|
|
||||
after_transition pending: :running do |build|
|
||||
build.execute_hooks
|
||||
end
|
||||
|
||||
after_transition any => [:success, :failed, :canceled] do |build, transition|
|
||||
return unless build.project
|
||||
# We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
|
||||
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.commit.create_next_builds(build)
|
||||
build.execute_hooks
|
||||
end
|
||||
end
|
||||
|
||||
def ignored?
|
||||
failed? && allow_failure?
|
||||
end
|
||||
|
||||
def retryable?
|
||||
project.builds_enabled? && commands.present?
|
||||
end
|
||||
@@ -179,6 +182,7 @@ module Ci
|
||||
end
|
||||
|
||||
def update_coverage
|
||||
return unless project
|
||||
coverage_regex = project.build_coverage_regex
|
||||
return unless coverage_regex
|
||||
coverage = extract_coverage(trace, coverage_regex)
|
||||
@@ -203,6 +207,10 @@ module Ci
|
||||
end
|
||||
end
|
||||
|
||||
def has_trace?
|
||||
raw_trace.present?
|
||||
end
|
||||
|
||||
def raw_trace
|
||||
if File.file?(path_to_trace)
|
||||
File.read(path_to_trace)
|
||||
@@ -330,6 +338,7 @@ module Ci
|
||||
end
|
||||
|
||||
def execute_hooks
|
||||
return unless project
|
||||
build_data = Gitlab::BuildDataBuilder.build(self)
|
||||
project.execute_hooks(build_data.dup, :build_hooks)
|
||||
project.execute_services(build_data.dup, :build_hooks)
|
||||
@@ -359,6 +368,33 @@ module Ci
|
||||
Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
|
||||
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
|
||||
|
||||
def yaml_variables
|
||||
|
||||
@@ -22,6 +22,7 @@ module Ci
|
||||
extend Ci::Model
|
||||
|
||||
LAST_CONTACT_TIME = 5.minutes.ago
|
||||
AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
|
||||
|
||||
has_many :builds, class_name: 'Ci::Build'
|
||||
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
|
||||
@@ -38,6 +39,11 @@ module Ci
|
||||
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
|
||||
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
|
||||
|
||||
def self.search(query)
|
||||
|
||||
@@ -215,6 +215,44 @@ class Commit
|
||||
ci_commit.try(:status) || :not_found
|
||||
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? && 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
|
||||
|
||||
def repo_changes
|
||||
|
||||
@@ -75,16 +75,16 @@ class CommitStatus < ActiveRecord::Base
|
||||
transition [:pending, :running] => :canceled
|
||||
end
|
||||
|
||||
after_transition pending: :running do |build, transition|
|
||||
build.update_attributes started_at: Time.now
|
||||
after_transition pending: :running do |commit_status|
|
||||
commit_status.update_attributes started_at: Time.now
|
||||
end
|
||||
|
||||
after_transition any => [:success, :failed, :canceled] do |build, transition|
|
||||
build.update_attributes finished_at: Time.now
|
||||
after_transition any => [:success, :failed, :canceled] do |commit_status|
|
||||
commit_status.update_attributes finished_at: Time.now
|
||||
end
|
||||
|
||||
after_transition [:pending, :running] => :success do |build, transition|
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build)
|
||||
after_transition [:pending, :running] => :success do |commit_status|
|
||||
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
|
||||
end
|
||||
|
||||
state :pending, value: 'pending'
|
||||
@@ -113,6 +113,10 @@ class CommitStatus < ActiveRecord::Base
|
||||
canceled? || success? || failed?
|
||||
end
|
||||
|
||||
def ignored?
|
||||
failed? && allow_failure?
|
||||
end
|
||||
|
||||
def duration
|
||||
if started_at && finished_at
|
||||
finished_at - started_at
|
||||
|
||||
@@ -69,10 +69,35 @@ module Issuable
|
||||
case method.to_s
|
||||
when 'milestone_due_asc' then order_milestone_due_asc
|
||||
when 'milestone_due_desc' then order_milestone_due_desc
|
||||
when 'downvotes_desc' then order_downvotes_desc
|
||||
when 'upvotes_desc' then order_upvotes_desc
|
||||
else
|
||||
order_by(method)
|
||||
end
|
||||
end
|
||||
|
||||
def order_downvotes_desc
|
||||
order_votes_desc('thumbsdown')
|
||||
end
|
||||
|
||||
def order_upvotes_desc
|
||||
order_votes_desc('thumbsup')
|
||||
end
|
||||
|
||||
def order_votes_desc(award_emoji_name)
|
||||
issuable_table = self.arel_table
|
||||
note_table = Note.arel_table
|
||||
|
||||
join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
|
||||
note_table[:noteable_id].eq(issuable_table[:id]).and(
|
||||
note_table[:noteable_type].eq(self.name).and(
|
||||
note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
|
||||
)
|
||||
)
|
||||
).join_sources
|
||||
|
||||
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
|
||||
end
|
||||
end
|
||||
|
||||
def today?
|
||||
|
||||
+12
-7
@@ -2,13 +2,14 @@
|
||||
#
|
||||
# Table name: labels
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# title :string(255)
|
||||
# color :string(255)
|
||||
# project_id :integer
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# template :boolean default(FALSE)
|
||||
# id :integer not null, primary key
|
||||
# title :string(255)
|
||||
# color :string(255)
|
||||
# project_id :integer
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# template :boolean default(FALSE)
|
||||
# description :string(255)
|
||||
#
|
||||
|
||||
class Label < ActiveRecord::Base
|
||||
@@ -85,6 +86,10 @@ class Label < ActiveRecord::Base
|
||||
issues.opened.count
|
||||
end
|
||||
|
||||
def closed_issues_count
|
||||
issues.closed.count
|
||||
end
|
||||
|
||||
def template?
|
||||
template
|
||||
end
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
# merge_params :text
|
||||
# merge_when_build_succeeds :boolean default(FALSE), not null
|
||||
# merge_user_id :integer
|
||||
# merge_commit_sha :string
|
||||
#
|
||||
|
||||
require Rails.root.join("app/models/commit")
|
||||
@@ -137,7 +138,7 @@ class MergeRequest < ActiveRecord::Base
|
||||
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
|
||||
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
|
||||
scope :of_projects, ->(ids) { where(target_project_id: ids) }
|
||||
scope :opened, -> { with_state(:opened) }
|
||||
scope :opened, -> { with_states(:opened, :reopened) }
|
||||
scope :merged, -> { with_state(:merged) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
scope :closed_and_merged, -> { with_states(:closed, :merged) }
|
||||
@@ -532,4 +533,12 @@ class MergeRequest < ActiveRecord::Base
|
||||
|
||||
[diff_base_commit, last_commit]
|
||||
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
|
||||
|
||||
@@ -27,6 +27,7 @@ class Milestone < ActiveRecord::Base
|
||||
|
||||
belongs_to :project
|
||||
has_many :issues
|
||||
has_many :labels, through: :issues
|
||||
has_many :merge_requests
|
||||
has_many :participants, through: :issues, source: :assignee
|
||||
|
||||
@@ -109,6 +110,19 @@ class Milestone < ActiveRecord::Base
|
||||
0
|
||||
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
|
||||
if due_date
|
||||
if due_date.past?
|
||||
|
||||
+4
-1
@@ -37,6 +37,8 @@ class Note < ActiveRecord::Base
|
||||
belongs_to :author, class_name: "User"
|
||||
belongs_to :updated_by, class_name: "User"
|
||||
|
||||
has_many :todos, dependent: :destroy
|
||||
|
||||
delegate :name, to: :project, prefix: true
|
||||
delegate :name, :email, to: :author, prefix: true
|
||||
|
||||
@@ -375,6 +377,7 @@ class Note < ActiveRecord::Base
|
||||
#
|
||||
def set_award!
|
||||
return unless awards_supported? && contains_emoji_only?
|
||||
|
||||
self.is_award = true
|
||||
self.note = award_emoji_name
|
||||
end
|
||||
@@ -382,7 +385,7 @@ class Note < ActiveRecord::Base
|
||||
private
|
||||
|
||||
def awards_supported?
|
||||
noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
|
||||
(noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)) && !for_diff_line?
|
||||
end
|
||||
|
||||
def contains_emoji_only?
|
||||
|
||||
@@ -382,6 +382,10 @@ class Project < ActiveRecord::Base
|
||||
external_import? || forked?
|
||||
end
|
||||
|
||||
def no_import?
|
||||
import_status == 'none'
|
||||
end
|
||||
|
||||
def external_import?
|
||||
import_url.present?
|
||||
end
|
||||
|
||||
@@ -136,7 +136,7 @@ class ProjectTeam
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
# This method assumes project and group members are eager loaded for optimal
|
||||
|
||||
+71
-12
@@ -23,13 +23,11 @@ class Repository
|
||||
def raw_repository
|
||||
return nil unless path_with_namespace
|
||||
|
||||
@raw_repository ||= begin
|
||||
repo = Gitlab::Git::Repository.new(path_to_repo)
|
||||
repo.autocrlf = :input
|
||||
repo
|
||||
rescue Gitlab::Git::Repository::NoRepository
|
||||
nil
|
||||
end
|
||||
@raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
|
||||
end
|
||||
|
||||
def update_autocrlf_option
|
||||
raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
|
||||
end
|
||||
|
||||
# Return absolute path to repository
|
||||
@@ -40,7 +38,12 @@ class Repository
|
||||
end
|
||||
|
||||
def exists?
|
||||
raw_repository
|
||||
return false unless raw_repository
|
||||
|
||||
raw_repository.rugged
|
||||
true
|
||||
rescue Gitlab::Git::Repository::NoRepository
|
||||
false
|
||||
end
|
||||
|
||||
def empty?
|
||||
@@ -67,7 +70,7 @@ class Repository
|
||||
end
|
||||
|
||||
def commit(id = 'HEAD')
|
||||
return nil unless raw_repository
|
||||
return nil unless exists?
|
||||
commit = Gitlab::Git::Commit.find(raw_repository, id)
|
||||
commit = Commit.new(commit, @project) if commit
|
||||
commit
|
||||
@@ -236,6 +239,19 @@ class Repository
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
# Expires _all_ caches, including those that would normally only be expired
|
||||
# under specific conditions.
|
||||
def expire_all_caches!
|
||||
expire_cache
|
||||
expire_root_ref_cache
|
||||
expire_emptiness_caches
|
||||
expire_has_visible_content_cache
|
||||
end
|
||||
|
||||
def expire_branch_cache(branch_name = nil)
|
||||
@@ -258,6 +274,14 @@ class Repository
|
||||
@root_ref = nil
|
||||
end
|
||||
|
||||
# Expires the cache(s) used to determine if a repository is empty or not.
|
||||
def expire_emptiness_caches
|
||||
cache.expire(:empty?)
|
||||
@empty = nil
|
||||
|
||||
expire_has_visible_content_cache
|
||||
end
|
||||
|
||||
def expire_has_visible_content_cache
|
||||
cache.expire(:has_visible_content?)
|
||||
@has_visible_content = nil
|
||||
@@ -599,6 +623,34 @@ class Repository
|
||||
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)
|
||||
branch_commit = commit(branch_name)
|
||||
root_ref_commit = commit(root_ref)
|
||||
@@ -611,6 +663,8 @@ class Repository
|
||||
end
|
||||
|
||||
def merge_base(first_commit_id, second_commit_id)
|
||||
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
|
||||
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
|
||||
rugged.merge_base(first_commit_id, second_commit_id)
|
||||
rescue Rugged::ReferenceError
|
||||
nil
|
||||
@@ -674,12 +728,15 @@ class Repository
|
||||
end
|
||||
|
||||
def commit_with_hooks(current_user, branch)
|
||||
update_autocrlf_option
|
||||
|
||||
oldrev = Gitlab::Git::BLANK_SHA
|
||||
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
|
||||
target_branch = find_branch(branch)
|
||||
was_empty = empty?
|
||||
|
||||
unless was_empty
|
||||
oldrev = find_branch(branch).target
|
||||
if !was_empty && target_branch
|
||||
oldrev = target_branch.target
|
||||
end
|
||||
|
||||
with_tmp_ref(oldrev) do |tmp_ref|
|
||||
@@ -691,7 +748,7 @@ class Repository
|
||||
end
|
||||
|
||||
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
|
||||
if was_empty
|
||||
if was_empty || !target_branch
|
||||
# Create branch
|
||||
rugged.references.create(ref, newrev)
|
||||
else
|
||||
@@ -706,6 +763,8 @@ class Repository
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
newrev
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# == 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] => :done
|
||||
end
|
||||
|
||||
state :pending
|
||||
state :done
|
||||
end
|
||||
|
||||
def body
|
||||
if note.present?
|
||||
note.note
|
||||
else
|
||||
target.title
|
||||
end
|
||||
end
|
||||
end
|
||||
+1
-1
@@ -143,7 +143,7 @@ class User < ActiveRecord::Base
|
||||
has_one :abuse_report, dependent: :destroy
|
||||
has_many :spam_logs, dependent: :destroy
|
||||
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
|
||||
|
||||
has_many :todos, dependent: :destroy
|
||||
|
||||
#
|
||||
# Validations
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
class ArchiveRepositoryService
|
||||
attr_reader :project, :ref, :format
|
||||
|
||||
def initialize(project, ref, format)
|
||||
format ||= 'tar.gz'
|
||||
@project, @ref, @format = project, ref, format.downcase
|
||||
end
|
||||
|
||||
def execute(options = {})
|
||||
RepositoryArchiveCacheWorker.perform_async
|
||||
|
||||
metadata = project.repository.archive_metadata(ref, storage_path, format)
|
||||
raise "Repository or ref not found" if metadata.empty?
|
||||
|
||||
metadata
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def storage_path
|
||||
Gitlab.config.gitlab.repository_downloads_path
|
||||
end
|
||||
end
|
||||
@@ -23,6 +23,10 @@ class BaseService
|
||||
EventCreateService.new
|
||||
end
|
||||
|
||||
def todo_service
|
||||
TodoService.new
|
||||
end
|
||||
|
||||
def log_info(message)
|
||||
Gitlab::AppLogger.info message
|
||||
end
|
||||
|
||||
@@ -34,6 +34,7 @@ module Ci
|
||||
|
||||
build = commit.builds.create!(build_attrs)
|
||||
build.execute_hooks
|
||||
build
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
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
|
||||
@@ -1,10 +1,10 @@
|
||||
class GitPushService
|
||||
attr_accessor :project, :user, :push_data, :push_commits
|
||||
class GitPushService < BaseService
|
||||
attr_accessor :push_data, :push_commits
|
||||
include Gitlab::CurrentSettings
|
||||
include Gitlab::Access
|
||||
|
||||
# This method will be called after each git update
|
||||
# and only if the provided user and project is present in GitLab.
|
||||
# and only if the provided user and project are present in GitLab.
|
||||
#
|
||||
# All callbacks for post receive action should be placed here.
|
||||
#
|
||||
@@ -15,67 +15,67 @@ class GitPushService
|
||||
# 4. Executes the project's web hooks
|
||||
# 5. Executes the project's services
|
||||
#
|
||||
def execute(project, user, oldrev, newrev, ref)
|
||||
@project, @user = project, user
|
||||
|
||||
branch_name = Gitlab::Git.ref_name(ref)
|
||||
|
||||
project.repository.expire_cache(branch_name)
|
||||
|
||||
if push_remove_branch?(ref, newrev)
|
||||
project.repository.expire_has_visible_content_cache
|
||||
def execute
|
||||
@project.repository.expire_cache(branch_name)
|
||||
|
||||
if push_remove_branch?
|
||||
@project.repository.expire_has_visible_content_cache
|
||||
@push_commits = []
|
||||
elsif push_to_new_branch?(ref, oldrev)
|
||||
project.repository.expire_has_visible_content_cache
|
||||
elsif push_to_new_branch?
|
||||
@project.repository.expire_has_visible_content_cache
|
||||
|
||||
# Re-find the pushed commits.
|
||||
if is_default_branch?(ref)
|
||||
if is_default_branch?
|
||||
# Initial push to the default branch. Take the full history of that branch as "newly pushed".
|
||||
@push_commits = project.repository.commits(newrev)
|
||||
|
||||
# Ensure HEAD points to the default branch in case it is not master
|
||||
project.change_head(branch_name)
|
||||
|
||||
# Set protection on the default branch if configured
|
||||
if (current_application_settings.default_branch_protection != PROTECTION_NONE)
|
||||
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
|
||||
project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push })
|
||||
end
|
||||
process_default_branch
|
||||
else
|
||||
# Use the pushed commits that aren't reachable by the default branch
|
||||
# as a heuristic. This may include more commits than are actually pushed, but
|
||||
# that shouldn't matter because we check for existing cross-references later.
|
||||
@push_commits = project.repository.commits_between(project.default_branch, newrev)
|
||||
@push_commits = @project.repository.commits_between(@project.default_branch, params[:newrev])
|
||||
|
||||
# don't process commits for the initial push to the default branch
|
||||
process_commit_messages(ref)
|
||||
process_commit_messages
|
||||
end
|
||||
elsif push_to_existing_branch?(ref, oldrev)
|
||||
elsif push_to_existing_branch?
|
||||
# Collect data for this git push
|
||||
@push_commits = project.repository.commits_between(oldrev, newrev)
|
||||
process_commit_messages(ref)
|
||||
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
|
||||
process_commit_messages
|
||||
end
|
||||
|
||||
# Update merge requests that may be affected by this push. A new branch
|
||||
# could cause the last commit of a merge request to change.
|
||||
project.update_merge_requests(oldrev, newrev, ref, @user)
|
||||
|
||||
@push_data = build_push_data(oldrev, newrev, ref)
|
||||
|
||||
EventCreateService.new.push(project, user, @push_data)
|
||||
project.execute_hooks(@push_data.dup, :push_hooks)
|
||||
project.execute_services(@push_data.dup, :push_hooks)
|
||||
CreateCommitBuildsService.new.execute(project, @user, @push_data)
|
||||
ProjectCacheWorker.perform_async(project.id)
|
||||
update_merge_requests
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def update_merge_requests
|
||||
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
|
||||
|
||||
EventCreateService.new.push(@project, current_user, build_push_data)
|
||||
@project.execute_hooks(build_push_data.dup, :push_hooks)
|
||||
@project.execute_services(build_push_data.dup, :push_hooks)
|
||||
CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
|
||||
ProjectCacheWorker.perform_async(@project.id)
|
||||
end
|
||||
|
||||
def process_default_branch
|
||||
@push_commits = project.repository.commits(params[:newrev])
|
||||
|
||||
# Ensure HEAD points to the default branch in case it is not master
|
||||
project.change_head(branch_name)
|
||||
|
||||
# Set protection on the default branch if configured
|
||||
if (current_application_settings.default_branch_protection != PROTECTION_NONE)
|
||||
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
|
||||
@project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push })
|
||||
end
|
||||
end
|
||||
|
||||
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
|
||||
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
|
||||
def process_commit_messages(ref)
|
||||
is_default_branch = is_default_branch?(ref)
|
||||
def process_commit_messages
|
||||
is_default_branch = is_default_branch?
|
||||
|
||||
authors = Hash.new do |hash, commit|
|
||||
email = commit.author_email
|
||||
@@ -94,7 +94,7 @@ class GitPushService
|
||||
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
|
||||
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
|
||||
# a different branch.
|
||||
closed_issues = commit.closes_issues(user)
|
||||
closed_issues = commit.closes_issues(current_user)
|
||||
closed_issues.each do |issue|
|
||||
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
|
||||
end
|
||||
@@ -104,34 +104,38 @@ class GitPushService
|
||||
end
|
||||
end
|
||||
|
||||
def build_push_data(oldrev, newrev, ref)
|
||||
Gitlab::PushDataBuilder.
|
||||
build(project, user, oldrev, newrev, ref, push_commits)
|
||||
def build_push_data
|
||||
@push_data ||= Gitlab::PushDataBuilder.
|
||||
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
|
||||
end
|
||||
|
||||
def push_to_existing_branch?(ref, oldrev)
|
||||
def push_to_existing_branch?
|
||||
# Return if this is not a push to a branch (e.g. new commits)
|
||||
Gitlab::Git.branch_ref?(ref) && !Gitlab::Git.blank_ref?(oldrev)
|
||||
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
|
||||
end
|
||||
|
||||
def push_to_new_branch?(ref, oldrev)
|
||||
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev)
|
||||
def push_to_new_branch?
|
||||
Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:oldrev])
|
||||
end
|
||||
|
||||
def push_remove_branch?(ref, newrev)
|
||||
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev)
|
||||
def push_remove_branch?
|
||||
Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:newrev])
|
||||
end
|
||||
|
||||
def push_to_branch?(ref)
|
||||
Gitlab::Git.branch_ref?(ref)
|
||||
def push_to_branch?
|
||||
Gitlab::Git.branch_ref?(params[:ref])
|
||||
end
|
||||
|
||||
def is_default_branch?(ref)
|
||||
Gitlab::Git.branch_ref?(ref) &&
|
||||
(Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
|
||||
def is_default_branch?
|
||||
Gitlab::Git.branch_ref?(params[:ref]) &&
|
||||
(Gitlab::Git.ref_name(params[:ref]) == project.default_branch || project.default_branch.nil?)
|
||||
end
|
||||
|
||||
def commit_user(commit)
|
||||
commit.author || user
|
||||
commit.author || current_user
|
||||
end
|
||||
|
||||
def branch_name
|
||||
@branch_name ||= Gitlab::Git.ref_name(params[:ref])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,7 +54,7 @@ class IssuableBaseService < BaseService
|
||||
if params.present? && issuable.update_attributes(params.merge(updated_by: current_user))
|
||||
issuable.reset_events_cache
|
||||
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)
|
||||
execute_hooks(issuable, 'update')
|
||||
end
|
||||
@@ -71,6 +71,19 @@ class IssuableBaseService < BaseService
|
||||
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 = {})
|
||||
if issuable.previous_changes.include?('title')
|
||||
create_title_change_note(issuable, issuable.previous_changes['title'].first)
|
||||
|
||||
@@ -3,6 +3,7 @@ module Issues
|
||||
def execute(issue, commit = nil)
|
||||
if project.jira_tracker? && project.jira_service.active
|
||||
project.jira_service.execute(commit, issue)
|
||||
todo_service.close_issue(issue, current_user)
|
||||
return issue
|
||||
end
|
||||
|
||||
@@ -10,6 +11,7 @@ module Issues
|
||||
event_service.close_issue(issue, current_user)
|
||||
create_note(issue, commit)
|
||||
notification_service.close_issue(issue, current_user)
|
||||
todo_service.close_issue(issue, current_user)
|
||||
execute_hooks(issue, 'close')
|
||||
end
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ module Issues
|
||||
if issue.save
|
||||
issue.update_attributes(label_ids: label_params)
|
||||
notification_service.new_issue(issue, current_user)
|
||||
todo_service.new_issue(issue, current_user)
|
||||
event_service.open_issue(issue, current_user)
|
||||
issue.create_cross_references!(current_user)
|
||||
execute_hooks(issue, 'open')
|
||||
|
||||
@@ -4,7 +4,16 @@ module Issues
|
||||
update(issue)
|
||||
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')
|
||||
create_milestone_note(issue)
|
||||
end
|
||||
@@ -12,6 +21,7 @@ module Issues
|
||||
if issue.previous_changes.include?('assignee_id')
|
||||
create_assignee_note(issue)
|
||||
notification_service.reassigned_issue(issue, current_user)
|
||||
todo_service.reassigned_issue(issue, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ module MergeRequests
|
||||
if commits && commits.count == 1
|
||||
commit = commits.first
|
||||
merge_request.title = commit.title
|
||||
merge_request.description = commit.description.try(:strip)
|
||||
merge_request.description ||= commit.description.try(:strip)
|
||||
else
|
||||
merge_request.title = merge_request.source_branch.titleize.humanize
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ module MergeRequests
|
||||
event_service.close_mr(merge_request, current_user)
|
||||
create_note(merge_request)
|
||||
notification_service.close_mr(merge_request, current_user)
|
||||
todo_service.close_merge_request(merge_request, current_user)
|
||||
execute_hooks(merge_request, 'close')
|
||||
end
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ module MergeRequests
|
||||
class CreateService < MergeRequests::BaseService
|
||||
def execute
|
||||
# @project is used to determine whether the user can set the merge request's
|
||||
# assignee, milestone and labels. Whether they can depends on their
|
||||
# assignee, milestone and labels. Whether they can depends on their
|
||||
# permissions on the target project.
|
||||
source_project = @project
|
||||
@project = Project.find(params[:target_project_id]) if params[:target_project_id]
|
||||
@@ -18,6 +18,7 @@ module MergeRequests
|
||||
merge_request.update_attributes(label_ids: label_params)
|
||||
event_service.open_mr(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)
|
||||
execute_hooks(merge_request)
|
||||
end
|
||||
|
||||
@@ -34,7 +34,8 @@ module MergeRequests
|
||||
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
|
||||
merge_request.update(merge_error: "Something went wrong during merge")
|
||||
Rails.logger.error(e.message)
|
||||
|
||||
@@ -19,15 +19,19 @@ module MergeRequests
|
||||
end
|
||||
|
||||
# Triggers the automatic merge of merge_request once the build succeeds
|
||||
def trigger(build)
|
||||
merge_requests = merge_request_from(build)
|
||||
def trigger(commit_status)
|
||||
merge_requests = merge_request_from(commit_status)
|
||||
|
||||
merge_requests.each do |merge_request|
|
||||
next unless merge_request.merge_when_build_succeeds?
|
||||
next unless merge_request.mergeable?
|
||||
|
||||
if merge_request.ci_commit && merge_request.ci_commit.success? && merge_request.mergeable?
|
||||
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
|
||||
end
|
||||
ci_commit = merge_request.ci_commit
|
||||
next unless ci_commit
|
||||
next unless ci_commit.sha == commit_status.sha
|
||||
next unless ci_commit.success?
|
||||
|
||||
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,9 +49,16 @@ module MergeRequests
|
||||
|
||||
private
|
||||
|
||||
def merge_request_from(build)
|
||||
merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a
|
||||
merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a
|
||||
def merge_request_from(commit_status)
|
||||
branches = commit_status.ref
|
||||
|
||||
# This is for ref-less builds
|
||||
branches ||= @project.repository.branch_names_contains(commit_status.sha)
|
||||
|
||||
return [] if branches.blank?
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
@@ -14,7 +14,16 @@ module MergeRequests
|
||||
update(merge_request)
|
||||
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')
|
||||
create_branch_change_note(merge_request, 'target',
|
||||
merge_request.previous_changes['target_branch'].first,
|
||||
@@ -28,6 +37,7 @@ module MergeRequests
|
||||
if merge_request.previous_changes.include?('assignee_id')
|
||||
create_assignee_note(merge_request)
|
||||
notification_service.reassigned_merge_request(merge_request, current_user)
|
||||
todo_service.reassigned_merge_request(merge_request, current_user)
|
||||
end
|
||||
|
||||
if merge_request.previous_changes.include?('target_branch') ||
|
||||
|
||||
@@ -8,6 +8,7 @@ module Notes
|
||||
if note.save
|
||||
# Finish the harder work in the background
|
||||
NewNoteWorker.perform_in(2.seconds, note.id, params)
|
||||
TodoService.new.new_note(note, current_user)
|
||||
end
|
||||
|
||||
note
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
module Notes
|
||||
class PostProcessService
|
||||
|
||||
attr_accessor :note
|
||||
|
||||
def initialize(note)
|
||||
@@ -25,6 +24,5 @@ module Notes
|
||||
@note.project.execute_hooks(note_data, :note_hooks)
|
||||
@note.project.execute_services(note_data, :note_hooks)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,10 @@ module Notes
|
||||
note.create_new_cross_references!(current_user)
|
||||
note.reset_events_cache
|
||||
|
||||
if note.previous_changes.include?('note')
|
||||
TodoService.new.update_note(note, current_user)
|
||||
end
|
||||
|
||||
note
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,11 +16,15 @@ module Projects
|
||||
return false unless can?(current_user, :remove_project, project)
|
||||
|
||||
project.team.truncate
|
||||
project.repository.expire_cache unless project.empty_repo?
|
||||
|
||||
repo_path = project.path_with_namespace
|
||||
wiki_path = repo_path + '.wiki'
|
||||
|
||||
# Flush the cache for both repositories. This has to be done _before_
|
||||
# removing the physical repositories as some expiration code depends on
|
||||
# Git data (e.g. a list of branch names).
|
||||
flush_caches(project, wiki_path)
|
||||
|
||||
Project.transaction do
|
||||
project.destroy!
|
||||
|
||||
@@ -70,5 +74,13 @@ module Projects
|
||||
def removal_path(path)
|
||||
"#{path}+#{project.id}#{DELETED_FLAG}"
|
||||
end
|
||||
|
||||
def flush_caches(project, wiki_path)
|
||||
project.repository.expire_all_caches! if project.repository.exists?
|
||||
|
||||
wiki_repo = Repository.new(wiki_path, project)
|
||||
|
||||
wiki_repo.expire_all_caches! if wiki_repo.exists?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
# 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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user