Commit 05f8c585 authored by Achilleas Pipinellis's avatar Achilleas Pipinellis

Merge branch 'master' into adding_crime_security

parents a3de4665 ed777c7b
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased) v 8.4.0 (unreleased)
- Implement new UI for group page
- Implement search inside emoji picker
- Add API support for looking up a user by username (Stan Hu)
- Add project permissions to all project API endpoints (Stan Hu)
- Expose Git's version in the admin area
- Add "Frequently used" category to emoji picker
- Add CAS support (tduehr)
- Add link to merge request on build detail page.
v 8.3.2 (unreleased)
- Enable "Add key" button when user fills in a proper key
v 8.3.1
- Fix Error 500 when global milestones have slashes (Stan Hu)
- Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu)
- Fix LDAP identity and user retrieval when special characters are used
- Move Sidekiq-cron configuration to gitlab.yml
- Enable forcing Two-Factor authentication sitewide, with optional grace period
v 8.3.0
- Bump rack-attack to 4.3.1 for security fix (Stan Hu) - Bump rack-attack to 4.3.1 for security fix (Stan Hu)
- API support for starred projects for authorized user (Zeger-Jan van de Weg) - API support for starred projects for authorized user (Zeger-Jan van de Weg)
- Add open_issues_count to project API (Stan Hu) - Add open_issues_count to project API (Stan Hu)
...@@ -8,6 +28,8 @@ v 8.3.0 (unreleased) ...@@ -8,6 +28,8 @@ v 8.3.0 (unreleased)
- Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg) - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg)
- Provide better diagnostic message upon project creation errors (Stan Hu) - Provide better diagnostic message upon project creation errors (Stan Hu)
- Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu) - Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu)
- Remove api credentials from link to build_page
- Deprecate GitLabCiService making it to always be inactive
- Bump gollum-lib to 4.1.0 (Stan Hu) - Bump gollum-lib to 4.1.0 (Stan Hu)
- Fix broken group avatar upload under "New group" (Stan Hu) - Fix broken group avatar upload under "New group" (Stan Hu)
- Update project repositorize size and commit count during import:repos task (Stan Hu) - Update project repositorize size and commit count during import:repos task (Stan Hu)
...@@ -19,8 +41,10 @@ v 8.3.0 (unreleased) ...@@ -19,8 +41,10 @@ v 8.3.0 (unreleased)
- Fix 500 error when update group member permission - Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references - Recognize issue/MR/snippet/commit links as references
- Backport JIRA features from EE to CE
- Add ignore whitespace change option to commit view - Add ignore whitespace change option to commit view
- Fire update hook from GitLab - Fire update hook from GitLab
- Allow account unlock via email
- Style warning about mentioning many people in a comment - Style warning about mentioning many people in a comment
- Fix: sort milestones by due date once again (Greg Smethells) - Fix: sort milestones by due date once again (Greg Smethells)
- Migrate all CI::Services and CI::WebHooks to Services and WebHooks - Migrate all CI::Services and CI::WebHooks to Services and WebHooks
......
...@@ -358,7 +358,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -358,7 +358,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[core team]: https://about.gitlab.com/core-team/ [core team]: https://about.gitlab.com/core-team/
[getting help page]: https://about.gitlab.com/getting-help/ [getting help page]: https://about.gitlab.com/getting-help/
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq [Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up+for+grabs [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 [medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues [ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues [ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
......
...@@ -23,6 +23,7 @@ gem 'devise-async', '~> 0.9.0' ...@@ -23,6 +23,7 @@ gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0' gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2' gem 'omniauth', '~> 1.2.2'
gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 3.0.0' gem 'omniauth-facebook', '~> 3.0.0'
gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
...@@ -101,6 +102,9 @@ gem 'wikicloth', '0.8.1' ...@@ -101,6 +102,9 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2' gem 'asciidoctor', '~> 1.5.2'
gem 'rouge', '~> 1.10.1' gem 'rouge', '~> 1.10.1'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
gem 'nokogiri', '1.6.7.1'
# Diffs # Diffs
gem 'diffy', '~> 3.0.3' gem 'diffy', '~> 3.0.3'
...@@ -168,7 +172,7 @@ gem 'd3_rails', '~> 3.5.5' ...@@ -168,7 +172,7 @@ gem 'd3_rails', '~> 3.5.5'
gem "cal-heatmap-rails", "~> 0.0.1" gem "cal-heatmap-rails", "~> 0.0.1"
# underscore-rails # underscore-rails
gem "underscore-rails", "~> 1.4.4" gem "underscore-rails", "~> 1.8.0"
# Sanitize user input # Sanitize user input
gem "sanitize", '~> 2.0' gem "sanitize", '~> 2.0'
...@@ -186,7 +190,7 @@ gem 'mousetrap-rails', '~> 1.4.6' ...@@ -186,7 +190,7 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.3' gem 'charlock_holmes', '~> 0.7.3'
gem "sass-rails", '~> 4.0.5' gem "sass-rails", '~> 5.0.0'
gem "coffee-rails", '~> 4.1.0' gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.7.2' gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0' gem 'turbolinks', '~> 2.5.0'
...@@ -198,9 +202,9 @@ gem 'font-awesome-rails', '~> 4.2' ...@@ -198,9 +202,9 @@ gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.2.0' gem 'gitlab_emoji', '~> 0.2.0'
gem 'gon', '~> 6.0.1' gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 3.1.3' gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3' gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 4.2.1' gem 'jquery-ui-rails', '~> 5.0.0'
gem 'nprogress-rails', '~> 0.1.6.7' gem 'nprogress-rails', '~> 0.1.6.7'
gem 'raphael-rails', '~> 2.1.2' gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.2.0' gem 'request_store', '~> 1.2.0'
......
...@@ -372,15 +372,16 @@ GEM ...@@ -372,15 +372,16 @@ GEM
inflecto (0.0.2) inflecto (0.0.2)
ipaddress (0.8.0) ipaddress (0.8.0)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
jquery-rails (3.1.4) jquery-rails (4.0.5)
railties (>= 3.0, < 5.0) rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3) jquery-scrollto-rails (1.4.3)
railties (> 3.1, < 5.0) railties (> 3.1, < 5.0)
jquery-turbolinks (2.1.0) jquery-turbolinks (2.1.0)
railties (>= 3.1.0) railties (>= 3.1.0)
turbolinks turbolinks
jquery-ui-rails (4.2.1) jquery-ui-rails (5.0.5)
railties (>= 3.2.16) railties (>= 3.2.16)
json (1.8.3) json (1.8.3)
jwt (1.5.2) jwt (1.5.2)
...@@ -420,7 +421,7 @@ GEM ...@@ -420,7 +421,7 @@ GEM
grape grape
newrelic_rpm newrelic_rpm
newrelic_rpm (3.9.4.245) newrelic_rpm (3.9.4.245)
nokogiri (1.6.7) nokogiri (1.6.7.1)
mini_portile2 (~> 2.0.0.rc2) mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7) nprogress-rails (0.1.6.7)
oauth (0.4.7) oauth (0.4.7)
...@@ -439,6 +440,10 @@ GEM ...@@ -439,6 +440,10 @@ GEM
multi_json (~> 1.7) multi_json (~> 1.7)
omniauth (~> 1.1) omniauth (~> 1.1)
omniauth-oauth (~> 1.0) omniauth-oauth (~> 1.0)
omniauth-cas3 (1.1.3)
addressable (~> 2.3)
nokogiri (~> 1.6.6)
omniauth (~> 1.2)
omniauth-facebook (3.0.0) omniauth-facebook (3.0.0)
omniauth-oauth2 (~> 1.2) omniauth-oauth2 (~> 1.2)
omniauth-github (1.1.2) omniauth-github (1.1.2)
...@@ -643,12 +648,13 @@ GEM ...@@ -643,12 +648,13 @@ GEM
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.2.19) sass (3.4.20)
sass-rails (4.0.5) sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 5.0)
sass (~> 3.2.2) sass (~> 3.1)
sprockets (~> 2.8, < 3.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (~> 2.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sawyer (0.6.0) sawyer (0.6.0)
addressable (~> 2.3.5) addressable (~> 2.3.5)
faraday (~> 0.8, < 0.10) faraday (~> 0.8, < 0.10)
...@@ -763,7 +769,7 @@ GEM ...@@ -763,7 +769,7 @@ GEM
uglifier (2.7.2) uglifier (2.7.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)
underscore-rails (1.4.4) underscore-rails (1.8.3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.1) unf_ext (0.0.7.1)
...@@ -874,10 +880,10 @@ DEPENDENCIES ...@@ -874,10 +880,10 @@ DEPENDENCIES
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
httparty (~> 0.13.3) httparty (~> 0.13.3)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 3.1.3) jquery-rails (~> 4.0.0)
jquery-scrollto-rails (~> 1.4.3) jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.1.0) jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 4.2.1) jquery-ui-rails (~> 5.0.0)
kaminari (~> 0.16.3) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener (~> 1.1.2)
mail_room (~> 0.6.1) mail_room (~> 0.6.1)
...@@ -888,11 +894,13 @@ DEPENDENCIES ...@@ -888,11 +894,13 @@ DEPENDENCIES
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic-grape newrelic-grape
newrelic_rpm (~> 3.9.4.245) newrelic_rpm (~> 3.9.4.245)
nokogiri (= 1.6.7.1)
nprogress-rails (~> 0.1.6.7) nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.7.0) octokit (~> 3.7.0)
omniauth (~> 1.2.2) omniauth (~> 1.2.2)
omniauth-bitbucket (~> 0.0.2) omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 3.0.0) omniauth-facebook (~> 3.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
...@@ -928,7 +936,7 @@ DEPENDENCIES ...@@ -928,7 +936,7 @@ DEPENDENCIES
rubocop (~> 0.35.0) rubocop (~> 0.35.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 4.0.5) sass-rails (~> 5.0.0)
sdoc (~> 0.3.20) sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
...@@ -957,7 +965,7 @@ DEPENDENCIES ...@@ -957,7 +965,7 @@ DEPENDENCIES
tinder (~> 1.10.0) tinder (~> 1.10.0)
turbolinks (~> 2.5.0) turbolinks (~> 2.5.0)
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.4.4) underscore-rails (~> 1.8.0)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.8.2) unicorn (~> 4.8.2)
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.2)
......
8.3.0.pre 8.4.0.pre
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
# the compiled file. # the compiled file.
# #
#= require jquery #= require jquery
#= require jquery.ui.all #= require jquery-ui
#= require jquery_ujs #= require jquery_ujs
#= require jquery.cookie #= require jquery.cookie
#= require jquery.endless-scroll #= require jquery.endless-scroll
......
class @AwardsHandler class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
$(".add-award").click (event)->
event.stopPropagation()
event.preventDefault()
$(".emoji-menu").show()
$("html").click ->
if !$(event.target).closest(".emoji-menu").length
if $(".emoji-menu").is(":visible")
$(".emoji-menu").hide()
@renderFrequentlyUsedBlock()
@setupSearch()
addAward: (emoji) -> addAward: (emoji) ->
emoji = @normilizeEmojiName(emoji) emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, => @postEmoji emoji, =>
@addAwardToEmojiBar(emoji) @addAwardToEmojiBar(emoji)
addAwardToEmojiBar: (emoji, custom_path = '') -> $(".emoji-menu").hide()
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
emoji = @normilizeEmojiName(emoji) emoji = @normilizeEmojiName(emoji)
if @exist(emoji) if @exist(emoji)
if @isActive(emoji) if @isActive(emoji)
...@@ -17,7 +33,7 @@ class @AwardsHandler ...@@ -17,7 +33,7 @@ class @AwardsHandler
counter.parent().addClass("active") counter.parent().addClass("active")
@addMeToAuthorList(emoji) @addMeToAuthorList(emoji)
else else
@createEmoji(emoji, custom_path) @createEmoji(emoji)
exist: (emoji) -> exist: (emoji) ->
@findEmojiIcon(emoji).length > 0 @findEmojiIcon(emoji).length > 0
...@@ -54,35 +70,39 @@ class @AwardsHandler ...@@ -54,35 +70,39 @@ class @AwardsHandler
resetTooltip: (award) -> resetTooltip: (award) ->
award.tooltip("destroy") award.tooltip("destroy")
# "destroy" call is asynchronous, this is why we need to set timeout. # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (-> setTimeout (->
award.tooltip() award.tooltip()
), 200 ), 200
createEmoji: (emoji, custom_path) -> createEmoji: (emoji) ->
emojiCssClass = @resolveNameToCssClass(emoji)
nodes = [] nodes = []
nodes.push("<div class='award active' title='me'>") nodes.push("<div class='award active' title='me'>")
nodes.push("<div class='icon' data-emoji='" + emoji + "'>") nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>")
nodes.push(@getImage(emoji, custom_path)) nodes.push("<div class='counter'>1</div>")
nodes.push("</div>") nodes.push("</div>")
nodes.push("<div class='counter'>1")
nodes.push("</div></div>")
$(".awards-controls").before(nodes.join("\n")) emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji)
$(".award").tooltip() $(".award").tooltip()
getImage: (emoji, custom_path) -> resolveNameToCssClass: (emoji) ->
if custom_path emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
$("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
if emoji_icon.length > 0
unicodeName = emoji_icon.data("unicode-name")
else else
$("li[data-emoji='" + emoji + "']").html() # Find by alias
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name")
"emoji-#{unicodeName}"
postEmoji: (emoji, callback) -> postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: { $.post @post_emoji_url, { note: {
note: ":" + emoji + ":" note: ":#{emoji}:"
noteable_type: @noteable_type noteable_type: @noteable_type
noteable_id: @noteable_id noteable_id: @noteable_id
}},(data) -> }},(data) ->
...@@ -90,7 +110,7 @@ class @AwardsHandler ...@@ -90,7 +110,7 @@ class @AwardsHandler
callback.call() callback.call()
findEmojiIcon: (emoji) -> findEmojiIcon: (emoji) ->
$(".icon[data-emoji='" + emoji + "']") $(".award [data-emoji='#{emoji}']")
scrollToAwards: -> scrollToAwards: ->
$('body, html').animate({ $('body, html').animate({
...@@ -99,3 +119,46 @@ class @AwardsHandler ...@@ -99,3 +119,46 @@ class @AwardsHandler
normilizeEmojiName: (emoji) -> normilizeEmojiName: (emoji) ->
@aliases[emoji] || emoji @aliases[emoji] || emoji
addEmojiToFrequentlyUsedList: (emoji) ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
frequently_used_emojis.push(emoji)
$.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
getFrequentlyUsedEmojis: ->
frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
frequently_used_emojis = ["thumbsup", "thumbsdown"].concat(frequently_used_emojis)
_.compact(_.uniq(frequently_used_emojis))
renderFrequentlyUsedBlock: ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
ul = $("<ul>")
for emoji in frequently_used_emojis
do (emoji) ->
$(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
$("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
setupSearch: ->
$("input.emoji-search").keyup (ev) =>
term = $(ev.target).val()
# Clean previous search results
$("ul.emoji-search,h5.emoji-search").remove()
if term
# Generate a search result block
h5 = $("<h5>").text("Search results").addClass("emoji-search")
found_emojis = @searchEmojis(term).show()
ul = $("<ul>").addClass("emoji-search").append(found_emojis)
$(".emoji-menu-content ul, .emoji-menu-content h5").hide()
$(".emoji-menu-content").append(h5).append(ul)
else
$(".emoji-menu-content").children().show()
searchEmojis: (term)->
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
...@@ -35,7 +35,7 @@ class @BlobFileDropzone ...@@ -35,7 +35,7 @@ class @BlobFileDropzone
return return
this.on 'sending', (file, xhr, formData) -> this.on 'sending', (file, xhr, formData) ->
formData.append('new_branch', form.find('.js-new-branch').val()) formData.append('target_branch', form.find('.js-target-branch').val())
formData.append('create_merge_request', form.find('.js-create-merge-request').val()) formData.append('create_merge_request', form.find('.js-create-merge-request').val())
formData.append('commit_message', form.find('.js-commit-message').val()) formData.append('commit_message', form.find('.js-commit-message').val())
return return
......
...@@ -18,7 +18,7 @@ class @MergeRequestWidget ...@@ -18,7 +18,7 @@ class @MergeRequestWidget
if data.state == "merged" if data.state == "merged"
urlSuffix = if deleteSourceBranch then '?delete_source=true' else '' urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
window.location.href = window.location.href + urlSuffix window.location.href = window.location.pathname + urlSuffix
else if data.merge_error else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>") $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else else
......
class @NewBranchForm
constructor: (form, availableRefs) ->
@branchNameError = form.find('.js-branch-name-error')
@name = form.find('.js-branch-name')
@ref = form.find('#ref')
@setupAvailableRefs(availableRefs)
@setupRestrictions()
@addBinding()
@init()
addBinding: ->
@name.on 'blur', @validate
init: ->
@name.trigger 'blur' if @name.val().length > 0
setupAvailableRefs: (availableRefs) ->
@ref.autocomplete
source: availableRefs,
minLength: 1
setupRestrictions: ->
startsWith = {
pattern: /^(\/|\.)/g,
prefix: "can't start with",
conjunction: "or"
}
endsWith = {
pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in",
conjunction: "or"
}
invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g
prefix: "can't contain",
conjunction: ", "
}
single = {
pattern: /^@+$/g
prefix: "can't be",
conjunction: "or"
}
@restrictions = [startsWith, invalid, endsWith, single]
validate: =>
@branchNameError.empty()
unique = (values, value) ->
values.push(value) unless value in values
values
formatter = (values, restriction) ->
formatted = values.map (value) ->
switch
when /\s/.test value then 'spaces'
when /\/{2,}/g.test value then 'consecutive slashes'
else "'#{value}'"
"#{restriction.prefix} #{formatted.join(restriction.conjunction)}"
validator = (errors, restriction) =>
matched = @name.val().match(restriction.pattern)
if matched
errors.concat formatter(matched.reduce(unique, []), restriction)
else
errors
errors = @restrictions.reduce validator, []
if errors.length > 0
errorMessage = $("<span/>").text(errors.join(', '))
@branchNameError.append(errorMessage)
class @NewCommitForm class @NewCommitForm
constructor: (form) -> constructor: (form) ->
@newBranch = form.find('.js-new-branch') @newBranch = form.find('.js-target-branch')
@originalBranch = form.find('.js-original-branch') @originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request') @createMergeRequest = form.find('.js-create-merge-request')
@createMergeRequestContainer = form.find('.js-create-merge-request-container') @createMergeRequestContainer = form.find('.js-create-merge-request-container')
......
...@@ -127,7 +127,7 @@ class @Notes ...@@ -127,7 +127,7 @@ class @Notes
@initTaskList() @initTaskList()
if note.award if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path) awards_handler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards() awards_handler.scrollToAwards()
### ###
......
class @Project class @Project
constructor: -> constructor: ->
# Git protocol switcher # Git protocol switcher
$('.js-protocol-switch').click -> $('ul.clone-options-dropdown a').click ->
return if $(@).hasClass('active') return if $(@).hasClass('active')
...@@ -10,7 +10,8 @@ class @Project ...@@ -10,7 +10,8 @@ class @Project
# Add the active class for the clicked button # Add the active class for the clicked button
$(@).toggleClass('active') $(@).toggleClass('active')
url = $(@).data('clone') url = $("#project_clone").val()
console.log("url",url)
# Update the input field # Update the input field
$('#project_clone').val(url) $('#project_clone').val(url)
......
...@@ -8,17 +8,17 @@ class @ProjectsList ...@@ -8,17 +8,17 @@ class @ProjectsList
$(".projects-list-filter").keyup -> $(".projects-list-filter").keyup ->
terms = $(this).val() terms = $(this).val()
uiBox = $(this).closest('.projects-list-holder') uiBox = $('div.projects-list-holder')
if terms == "" || terms == undefined if terms == "" || terms == undefined
uiBox.find(".projects-list li").show() uiBox.find("ul.projects-list li").show()
else else
uiBox.find(".projects-list li").each (index) -> uiBox.find("ul.projects-list li").each (index) ->
name = $(this).find(".filter-title").text() name = $(this).find("span.filter-title").text()
if name.toLowerCase().search(terms.toLowerCase()) == -1 if name.toLowerCase().search(terms.toLowerCase()) == -1
$(this).hide() $(this).hide()
else else
$(this).show() $(this).show()
uiBox.find(".projects-list li.bottom").hide() uiBox.find("ul.projects-list li.bottom").hide()
class @Star
constructor: ->
$('.project-home-panel .toggle-star').on('ajax:success', (e, data, status, xhr) ->
$this = $(this)
$starSpan = $this.find('span')
$starIcon = $this.find('i')
toggleStar = (isStarred) ->
$this.parent().find('span.count').text data.star_count
if isStarred
$starSpan.removeClass('starred').text 'Star'
$starIcon.removeClass('fa-star').addClass 'fa-star-o'
else
$starSpan.addClass('starred').text 'Unstar'
$starIcon.removeClass('fa-star-o').addClass 'fa-star'
return
toggleStar $starSpan.hasClass('starred')
return
).on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
return
\ No newline at end of file
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory * This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope. * the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery.ui.datepicker *= require jquery-ui/datepicker
*= require jquery.ui.autocomplete *= require jquery-ui/autocomplete
*= require jquery.atwho *= require jquery.atwho
*= require select2 *= require select2
*= require_self *= require_self
......
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
.cover-block { .cover-block {
text-align: center; text-align: center;
background: #f7f8fa; background: $background-color;
margin: -$gl-padding; margin: -$gl-padding;
margin-bottom: 0; margin-bottom: 0;
padding: 44px $gl-padding; padding: 44px $gl-padding;
......
@mixin btn-default { @mixin btn-default {
@include border-radius(2px); @include border-radius(3px);
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
text-transform: uppercase; font-size: 15px;
font-size: 13px; font-weight: 500;
font-weight: 600;
line-height: 18px; line-height: 18px;
padding: 11px $gl-padding; padding: 11px $gl-padding;
letter-spacing: .4px; letter-spacing: .4px;
...@@ -18,7 +17,7 @@ ...@@ -18,7 +17,7 @@
@mixin btn-middle { @mixin btn-middle {
@include btn-default; @include btn-default;
@include border-radius(2px); @include border-radius(3px);
padding: 11px 24px; padding: 11px 24px;
} }
...@@ -51,6 +50,10 @@ ...@@ -51,6 +50,10 @@
@include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF); @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF);
} }
@mixin btn-blue-medium {
@include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #FFFFFF);
}
@mixin btn-orange { @mixin btn-orange {
@include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF); @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF);
} }
...@@ -60,7 +63,7 @@ ...@@ -60,7 +63,7 @@
} }
@mixin btn-gray { @mixin btn-gray {
@include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, #313236); @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236);
} }
@mixin btn-white { @mixin btn-white {
...@@ -75,6 +78,10 @@ ...@@ -75,6 +78,10 @@
padding: 5px 10px; padding: 5px 10px;
} }
&.btn-nr {
padding: 7px 10px;
}
&.btn-xs { &.btn-xs {
padding: 1px 5px; padding: 1px 5px;
} }
...@@ -91,11 +98,15 @@ ...@@ -91,11 +98,15 @@
@include btn-gray; @include btn-gray;
} }
&.btn-primary, &.btn-primary {
@include btn-blue-medium;
}
&.btn-info { &.btn-info {
@include btn-blue; @include btn-blue;
} }
&.btn-close,
&.btn-warning { &.btn-warning {
@include btn-orange; @include btn-orange;
} }
...@@ -110,20 +121,8 @@ ...@@ -110,20 +121,8 @@
float: right; float: right;
} }
&.btn-close {
color: $gl-danger;
border-color: $gl-danger;
&:hover {
color: #B94A48;
}
}
&.btn-reopen { &.btn-reopen {
color: $gl-success; /* should be same as parent class for now */
border-color: $gl-success;
&:hover {
color: #468847;
}
} }
&.btn-grouped { &.btn-grouped {
......
...@@ -374,7 +374,7 @@ table { ...@@ -374,7 +374,7 @@ table {
} }
} }
.center-top-menu { .center-top-menu, .left-top-menu {
@include nav-menu; @include nav-menu;
text-align: center; text-align: center;
margin-top: 5px; margin-top: 5px;
...@@ -408,6 +408,11 @@ table { ...@@ -408,6 +408,11 @@ table {
} }
} }
.left-top-menu {
text-align: left;
border-bottom: 1px solid #EEE;
}
.center-middle-menu { .center-middle-menu {
@include nav-menu; @include nav-menu;
padding: 0; padding: 0;
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
.status-box { .status-box {
@include border-radius(2px); @include border-radius(3px);
display: block; display: block;
float: left; float: left;
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
} }
&.status-box-open { &.status-box-open {
background-color: #019875; background-color: $green-light;
color: #FFF; color: #FFF;
} }
......
...@@ -5,7 +5,7 @@ html { ...@@ -5,7 +5,7 @@ html {
} }
body { body {
background-color: #EAEBEC !important; background-color: #F3F3F3 !important;
&.navless { &.navless {
background-color: white !important; background-color: white !important;
......
...@@ -123,7 +123,6 @@ ...@@ -123,7 +123,6 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
margin-top: 5px;
height: 56px; height: 56px;
li { li {
...@@ -131,9 +130,9 @@ ...@@ -131,9 +130,9 @@
a { a {
padding: 14px; padding: 14px;
font-size: 17px; font-size: 15px;
line-height: 28px; line-height: 28px;
color: #7f8fa4; color: #959494;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
&:hover, &:active, &:focus { &:hover, &:active, &:focus {
...@@ -143,8 +142,8 @@ ...@@ -143,8 +142,8 @@
} }
&.active a { &.active a {
color: #4c4e54; color: #616060;
border-bottom: 2px solid #1cacfc; border-bottom: 2px solid #4688f1;
} }
.badge { .badge {
......
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
display: none; display: none;
} }
.center-top-menu { .center-top-menu, .left-top-menu {
li a { li a {
font-size: 14px; font-size: 14px;
padding: 19px 10px; padding: 19px 10px;
......
$hover: #FFFAF1; $hover: #faf9f9;
$gl-text-color: #54565B; $gl-text-color: #54565B;
$gl-text-green: #4A2; $gl-text-green: #4A2;
$gl-text-red: #D12F19; $gl-text-red: #D12F19;
$gl-text-orange: #D90; $gl-text-orange: #D90;
$gl-header-color: #4c4e54; $gl-header-color: #323232;
$gl-link-color: #333c48; $gl-link-color: #333c48;
$md-text-color: #444; $md-text-color: #444;
$md-link-color: #3084bb; $md-link-color: #3084bb;
...@@ -15,13 +15,14 @@ $sidebar_width: 230px; ...@@ -15,13 +15,14 @@ $sidebar_width: 230px;
$avatar_radius: 50%; $avatar_radius: 50%;
$code_font_size: 13px; $code_font_size: 13px;
$code_line_height: 1.5; $code_line_height: 1.5;
$border-color: #dce0e6; $border-color: #efeff1;
$table-border-color: #eef0f2; $table-border-color: #eef0f2;
$background-color: #F7F8FA; $background-color: #faf9f9;
$header-height: 58px; $header-height: 58px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$gl-gray: #7f8fa4; $gl-gray: #5a5a5a;
$gl-padding: 16px; $gl-padding: 16px;
$gl-padding-top:10px;
$gl-avatar-size: 46px; $gl-avatar-size: 46px;
/* /*
...@@ -29,12 +30,12 @@ $gl-avatar-size: 46px; ...@@ -29,12 +30,12 @@ $gl-avatar-size: 46px;
*/ */
$white-light: #FFFFFF; $white-light: #FFFFFF;
$white-normal: #DCE0E5; $white-normal: #ededed;
$white-dark: #E4E7ED; $white-dark: #ededed;
$gray-light: #F0F2F5; $gray-light: #f7f7f7;
$gray-normal: #DCE0E5; $gray-normal: #ededed;
$gray-dark: #E4E7ED; $gray-dark: #ededed;
$green-light: #31AF64; $green-light: #31AF64;
$green-normal: #2FAA60; $green-normal: #2FAA60;
...@@ -44,6 +45,10 @@ $blue-light: #2EA8E5; ...@@ -44,6 +45,10 @@ $blue-light: #2EA8E5;
$blue-normal: #2D9FD8; $blue-normal: #2D9FD8;
$blue-dark: #2897CE; $blue-dark: #2897CE;
$blue-medium-light: #3498CB;
$blue-medium: #2F8EBF;
$blue-medium-dark: #2D86B4;
$orange-light: #FC6443; $orange-light: #FC6443;
$orange-normal: #E75E40; $orange-normal: #E75E40;
$orange-dark: #CE5237; $orange-dark: #CE5237;
...@@ -52,11 +57,11 @@ $red-light: #F43263; ...@@ -52,11 +57,11 @@ $red-light: #F43263;
$red-normal: #E52C5A; $red-normal: #E52C5A;
$red-dark: #D22852; $red-dark: #D22852;
$border-white-light: #E3E7EC; $border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2; $border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF; $border-white-dark: #C6CACF;
$border-gray-light: #DCE0E5; $border-gray-light: #d1d1d1;
$border-gray-normal: #D6DAE2; $border-gray-normal: #D6DAE2;
$border-gray-dark: #C6CACF; $border-gray-dark: #C6CACF;
...@@ -76,6 +81,8 @@ $border-red-light: #E52C5A; ...@@ -76,6 +81,8 @@ $border-red-light: #E52C5A;
$border-red-normal: #D22852; $border-red-normal: #D22852;
$border-red-dark: #CA264F; $border-red-dark: #CA264F;
/* header */
$light-grey-header: #faf9f9;
/* /*
* State colors: * State colors:
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
@include clearfix; @include clearfix;
line-height: 34px; line-height: 34px;
.emoji-icon {
width: 20px;
height: 20px;
margin: 7px 0 0 5px;
}
.award { .award {
@include border-radius(5px); @include border-radius(5px);
...@@ -40,6 +46,7 @@ ...@@ -40,6 +46,7 @@
} }
.awards-controls { .awards-controls {
position: relative;
margin-left: 10px; margin-left: 10px;
float: left; float: left;
...@@ -55,32 +62,64 @@ ...@@ -55,32 +62,64 @@
} }
} }
.awards-menu { .emoji-menu{
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
box-shadow: 0 6px 12px rgba(0,0,0,.175);
.emoji-menu-content {
padding: $gl-padding; padding: $gl-padding;
min-width: 214px; width: 300px;
height: 300px;
overflow-y: scroll;
h5 {
clear: left;
}
ul {
list-style-type: none;
margin-left: -20px;
margin-bottom: 20px;
overflow: auto;
}
input.emoji-search{
background: image-url("icon-search.png") 240px no-repeat;
}
> li { li {
cursor: pointer; cursor: pointer;
width: 30px; width: 30px;
height: 30px; height: 30px;
text-align: center; text-align: center;
float: left;
margin: 3px;
list-decorate: none;
@include border-radius(5px); @include border-radius(5px);
img {
margin-bottom: 2px;
}
&:hover { &:hover {
background-color: #ccc; background-color: #ccc;
} }
} }
} }
} }
.awards-menu{
li {
float: left;
margin: 3px;
}
} }
} }
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: #5c5d5e; color: #5c5d5e;
font-size: 16px; font-size: 16px;
line-height: 42px; line-height: 34px;
.author { .author {
color: #5c5d5e; color: #5c5d5e;
......
This diff is collapsed.
...@@ -75,16 +75,15 @@ ...@@ -75,16 +75,15 @@
.common-note-form { .common-note-form {
margin: 0; margin: 0;
background: #F7F8FA; background: #fff;
padding: $gl-padding; padding: $gl-padding;
margin-left: -$gl-padding; margin-left: -$gl-padding;
margin-right: -$gl-padding; margin-right: -$gl-padding;
border-top: 1px solid $border-color;
margin-bottom: -$gl-padding; margin-bottom: -$gl-padding;
} }
.note-form-actions { .note-form-actions {
background: #F9F9F9; background: #fff;
.note-form-option { .note-form-option {
margin-top: 8px; margin-top: 8px;
......
...@@ -128,7 +128,7 @@ ul.notes { ...@@ -128,7 +128,7 @@ ul.notes {
} }
&:last-child { &:last-child {
border-bottom: none; border-bottom: 1px solid $border-color;
} }
} }
} }
......
...@@ -91,21 +91,83 @@ ...@@ -91,21 +91,83 @@
} }
} }
.input-group { .git-clone-holder {
display: inline-table; display: inline-table;
position: relative; position: relative;
top: 17px;
} }
.project-repo-buttons { .project-repo-buttons {
margin-top: 12px; margin-top: 12px;
margin-bottom: 0px; margin-bottom: 0px;
.count-buttons {
display: block;
margin-bottom: 12px;
}
.btn { .btn {
@include btn-gray; @include btn-gray;
text-transform: none;
}
.count-with-arrow {
display: inline-block;
position: relative;
margin-left: 4px;
.arrow {
&:before {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 0;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: #dce0e5;
}
&:after {
content: '';
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 1px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: #FFF;
}
}
.count { .count {
@include btn-gray;
display: inline-block; display: inline-block;
background: white;
border-radius: 2px;
border-width: 1px;
border-style: solid;
font-size: 13px;
font-weight: 600;
line-height: 20px;
padding: 11px 16px;
letter-spacing: .4px;
padding: 10px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
white-space: nowrap;
margin: 0 11px 0px 4px;
&:hover {
background: #FFF;
}
} }
} }
} }
...@@ -125,6 +187,13 @@ ...@@ -125,6 +187,13 @@
margin-right: 45px; margin-right: 45px;
} }
.clone-options {
display: table-cell;
a.btn {
width: 100%;
}
}
.form-control { .form-control {
cursor: auto; cursor: auto;
@extend .monospace; @extend .monospace;
...@@ -335,6 +404,38 @@ ul.nav.nav-projects-tabs { ...@@ -335,6 +404,38 @@ ul.nav.nav-projects-tabs {
} }
} }
.top-area {
border-bottom: 1px solid #EEE;
margin: 0 -16px;
padding: 0 $gl-padding;
ul.left-top-menu {
display: inline-block;
width: 50%;
margin-bottom: 0px;
border-bottom: none;
}
.projects-search-form {
width: 50%;
display: inline-block;
float: right;
padding-top: 7px;
text-align: right;
.btn-green {
margin-top: -2px;
margin-left: 10px;
}
}
@media (max-width: $screen-xs-max) {
.projects-search-form {
padding-top: 15px;
}
}
}
.fork-namespaces { .fork-namespaces {
.fork-thumbnail { .fork-thumbnail {
text-align: center; text-align: center;
...@@ -412,11 +513,18 @@ pre.light-well { ...@@ -412,11 +513,18 @@ pre.light-well {
.projects-search-form { .projects-search-form {
margin: -$gl-padding; margin: -$gl-padding;
background-color: #f8fafc;
padding: $gl-padding; padding: $gl-padding;
margin-bottom: 0px; margin-bottom: 0px;
border-top: 1px solid #e7e9ed;
border-bottom: 1px solid #e7e9ed; input {
display: inline-block;
width: calc(100% - 151px);
}
.btn {
display: inline-block;
width: 135px;
}
} }
.git-empty { .git-empty {
......
...@@ -49,6 +49,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -49,6 +49,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_branch_protection, :default_branch_protection,
:signup_enabled, :signup_enabled,
:signin_enabled, :signin_enabled,
:require_two_factor_authentication,
:two_factor_grace_period,
:gravatar_enabled, :gravatar_enabled,
:twitter_sharing_enabled, :twitter_sharing_enabled,
:sign_in_text, :sign_in_text,
......
class Admin::IdentitiesController < Admin::ApplicationController class Admin::IdentitiesController < Admin::ApplicationController
before_action :user before_action :user
before_action :identity, except: :index before_action :identity, except: [:index, :new, :create]
def new
@identity = Identity.new
end
def create
@identity = Identity.new(identity_params)
@identity.user_id = user.id
if @identity.save
redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully created.'
else
render :new
end
end
def index def index
@identities = @user.identities @identities = @user.identities
......
...@@ -10,8 +10,10 @@ class ApplicationController < ActionController::Base ...@@ -10,8 +10,10 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user_from_token! before_action :authenticate_user_from_token!
before_action :authenticate_user! before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :reject_blocked! before_action :reject_blocked!
before_action :check_password_expiration before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check before_action :ldap_security_check
before_action :default_headers before_action :default_headers
before_action :add_gon_variables before_action :add_gon_variables
...@@ -202,12 +204,32 @@ class ApplicationController < ActionController::Base ...@@ -202,12 +204,32 @@ class ApplicationController < ActionController::Base
end end
end end
def validate_user_service_ticket!
return unless signed_in? && session[:service_tickets]
valid = session[:service_tickets].all? do |provider, ticket|
Gitlab::OAuth::Session.valid?(provider, ticket)
end
unless valid
session[:service_tickets] = nil
sign_out current_user
redirect_to new_user_session_path
end
end
def check_password_expiration def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
redirect_to new_profile_password_path and return redirect_to new_profile_password_path and return
end end
end end
def check_2fa_requirement
if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
redirect_to new_profile_two_factor_auth_path
end
end
def ldap_security_check def ldap_security_check
if current_user && current_user.requires_ldap_check? if current_user && current_user.requires_ldap_check?
unless Gitlab::LDAP::Access.allowed?(current_user) unless Gitlab::LDAP::Access.allowed?(current_user)
...@@ -342,6 +364,23 @@ class ApplicationController < ActionController::Base ...@@ -342,6 +364,23 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('git') current_application_settings.import_sources.include?('git')
end end
def two_factor_authentication_required?
current_application_settings.require_two_factor_authentication
end
def two_factor_grace_period
current_application_settings.two_factor_grace_period
end
def two_factor_grace_period_expired?
date = current_user.otp_grace_period_started_at
date && (date + two_factor_grace_period.hours) < Time.current
end
def skip_two_factor?
session[:skip_tfa] && session[:skip_tfa] > Time.current
end
def redirect_to_home_page_url? def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page # If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections # Don't redirect to the default URL to prevent endless redirections
......
...@@ -19,8 +19,10 @@ module Ci ...@@ -19,8 +19,10 @@ module Ci
@error = e.message @error = e.message
@status = false @status = false
rescue rescue
@error = "Undefined error" @error = 'Undefined error'
@status = false @status = false
ensure
render :show
end end
end end
end end
module CreatesCommit
extend ActiveSupport::Concern
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
set_commit_variables
commit_params = @commit_params.merge(
source_project: @project,
source_branch: @ref,
target_branch: @target_branch
)
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
respond_to do |format|
format.html { redirect_to success_path }
format.json { render json: { message: "success", filePath: success_path } }
end
else
flash[:alert] = result[:message]
respond_to do |format|
format.html do
if failure_view
render failure_view
else
redirect_to failure_path
end
end
format.json { render json: { message: "failed", filePath: failure_path } }
end
end
end
def authorize_edit_tree!
return if can?(current_user, :push_code, project)
return if current_user && current_user.already_forked?(project)
access_denied!
end
private
def new_merge_request_path
new_namespace_project_merge_request_path(
@mr_source_project.namespace,
@mr_source_project,
merge_request: {
source_project_id: @mr_source_project.id,
target_project_id: @mr_target_project.id,
source_branch: @mr_source_branch,
target_branch: @mr_target_branch
}
)
end
def different_project?
@mr_source_project != @mr_target_project
end
def different_branch?
@mr_source_branch != @mr_target_branch || different_project?
end
def create_merge_request?
params[:create_merge_request].present? && different_branch?
end
def set_commit_variables
@mr_source_branch = @target_branch
if can?(current_user, :push_code, @project)
# Edit file in this project
@tree_edit_project = @project
@mr_source_project = @project
if @project.forked?
# Merge request from this project to fork origin
@mr_target_project = @project.forked_from_project
@mr_target_branch = @mr_target_project.repository.root_ref
else
# Merge request to this project
@mr_target_project = @project
@mr_target_branch = @ref
end
else
# Edit file in fork
@tree_edit_project = current_user.fork_of(@project)
# Merge request from fork to this project
@mr_source_project = @tree_edit_project
@mr_target_project = @project
@mr_target_branch = @mr_target_project.repository.root_ref
end
end
end
module CreatesMergeRequestForCommit
extend ActiveSupport::Concern
def new_merge_request_path
if @project.forked?
target_project = @project.forked_from_project || @project
target_branch = target_project.repository.root_ref
else
target_project = @project
target_branch = @ref
end
new_namespace_project_merge_request_path(
@project.namespace,
@project,
merge_request: {
source_project_id: @project.id,
target_project_id: target_project.id,
source_branch: @new_branch,
target_branch: target_branch
}
)
end
def create_merge_request?
params[:create_merge_request] && @new_branch != @ref
end
end
class OmniauthCallbacksController < Devise::OmniauthCallbacksController class OmniauthCallbacksController < Devise::OmniauthCallbacksController
protect_from_forgery except: [:kerberos, :saml] protect_from_forgery except: [:kerberos, :saml, :cas3]
Gitlab.config.omniauth.providers.each do |provider| Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do define_method provider['name'] do
...@@ -42,6 +42,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -42,6 +42,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
render 'errors/omniauth_error', layout: "errors", status: 422 render 'errors/omniauth_error', layout: "errors", status: 422
end end
def cas3
ticket = params['ticket']
if ticket
handle_service_ticket oauth['provider'], ticket
end
handle_omniauth
end
private private
def handle_omniauth def handle_omniauth
...@@ -84,6 +92,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -84,6 +92,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to new_user_session_path redirect_to new_user_session_path
end end
def handle_service_ticket provider, ticket
Gitlab::OAuth::Session.create provider, ticket
session[:service_tickets] ||= {}
session[:service_tickets][provider] = ticket
end
def oauth def oauth
@oauth ||= request.env['omniauth.auth'] @oauth ||= request.env['omniauth.auth']
end end
......
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_2fa_requirement
def new def new
unless current_user.otp_secret unless current_user.otp_secret
current_user.otp_secret = User.generate_otp_secret(32) current_user.otp_secret = User.generate_otp_secret(32)
current_user.save! end
unless current_user.otp_grace_period_started_at && two_factor_grace_period
current_user.otp_grace_period_started_at = Time.current
end
current_user.save! if current_user.changed?
if two_factor_grace_period_expired?
flash.now[:alert] = 'You must configure Two-Factor Authentication in your account.'
else
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
flash.now[:alert] = "You must configure Two-Factor Authentication in your account until #{l(grace_period_deadline)}."
end end
@qr_code = build_qr_code @qr_code = build_qr_code
...@@ -34,6 +48,15 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -34,6 +48,15 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
redirect_to profile_account_path redirect_to profile_account_path
end end
def skip
if two_factor_grace_period_expired?
redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
else
session[:skip_tfa] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
redirect_to root_path
end
end
private private
def build_qr_code def build_qr_code
......
# Controller for viewing a file's blame # Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController class Projects::BlobController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include CreatesMergeRequestForCommit include CreatesCommit
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path # Raised when given an invalid file path
...@@ -9,21 +9,21 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -9,21 +9,21 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:destroy, :create] before_action :authorize_edit_tree!, only: [:new, :create, :edit, :update, :destroy]
before_action :assign_blob_vars before_action :assign_blob_vars
before_action :commit, except: [:new, :create] before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create] before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update] before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff] before_action :editor_variables, except: [:show, :preview, :diff]
before_action :after_edit_path, only: [:edit, :update]
def new def new
commit unless @repository.empty? commit unless @repository.empty?
end end
def create def create
create_commit(Files::CreateService, success_path: after_create_path, create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
success_path: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)),
failure_view: :new, failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end end
...@@ -36,6 +36,14 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -36,6 +36,14 @@ class Projects::BlobController < Projects::ApplicationController
end end
def update def update
after_edit_path =
if from_merge_request && @target_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
end
create_commit(Files::UpdateService, success_path: after_edit_path, create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit, failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
...@@ -50,15 +58,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -50,15 +58,10 @@ class Projects::BlobController < Projects::ApplicationController
end end
def destroy def destroy
result = Files::DeleteService.new(@project, current_user, @commit_params).execute create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch),
if result[:status] == :success failure_view: :show,
flash[:notice] = "Your changes have been successfully committed" failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
redirect_to after_destroy_path
else
flash[:alert] = result[:message]
render :show
end
end end
def diff def diff
...@@ -108,74 +111,13 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -108,74 +111,13 @@ class Projects::BlobController < Projects::ApplicationController
render_404 render_404
end end
def create_commit(service, success_path:, failure_view:, failure_path:)
result = service.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
respond_to do |format|
format.html { redirect_to success_path }
format.json { render json: { message: "success", filePath: success_path } }
end
else
flash[:alert] = result[:message]
respond_to do |format|
format.html { render failure_view }
format.json { render json: { message: "failed", filePath: failure_path } }
end
end
end
def after_create_path
@after_create_path ||=
if create_merge_request?
new_merge_request_path
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path))
end
end
def after_edit_path
@after_edit_path ||=
if create_merge_request?
new_merge_request_path
elsif from_merge_request && @new_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path))
end
end
def after_destroy_path
@after_destroy_path ||=
if create_merge_request?
new_merge_request_path
else
namespace_project_tree_path(@project.namespace, @project, @new_branch)
end
end
def from_merge_request def from_merge_request
# If blob edit was initiated from merge request page # If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end end
def sanitized_new_branch_name
sanitize(strip_tags(params[:new_branch]))
end
def editor_variables def editor_variables
@current_branch = @ref @target_branch = params[:target_branch]
@new_branch =
if params[:new_branch].present?
sanitized_new_branch_name
elsif ::Gitlab::GitAccess.new(current_user, @project).can_push_to_branch?(@ref)
@ref
else
@repository.next_patch_branch
end
@file_path = @file_path =
if action_name.to_s == 'create' if action_name.to_s == 'create'
...@@ -194,8 +136,6 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -194,8 +136,6 @@ class Projects::BlobController < Projects::ApplicationController
@commit_params = { @commit_params = {
file_path: @file_path, file_path: @file_path,
current_branch: @current_branch,
target_branch: @new_branch,
commit_message: params[:commit_message], commit_message: params[:commit_message],
file_content: params[:content], file_content: params[:content],
file_content_encoding: params[:encoding] file_content_encoding: params[:encoding]
......
...@@ -10,19 +10,35 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -10,19 +10,35 @@ class Projects::ForksController < Projects::ApplicationController
def create def create
namespace = Namespace.find(params[:namespace_key]) namespace = Namespace.find(params[:namespace_key])
@forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
@forked_project = namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
@forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
if @forked_project.saved? && @forked_project.forked? if @forked_project.saved? && @forked_project.forked?
if @forked_project.import_in_progress? if @forked_project.import_in_progress?
redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project) redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
else
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else else
redirect_to( redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project was successfully forked."
namespace_project_path(@forked_project.namespace, @forked_project), end
notice: 'Project was successfully forked.'
)
end end
else else
render :error render :error
end end
end end
private
def continue_params
continue_params = params[:continue]
if continue_params
continue_params.permit(:to, :notice, :notice_now)
else
nil
end
end
end end
class Projects::ImportsController < Projects::ApplicationController class Projects::ImportsController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :require_no_repo before_action :require_no_repo, except: :show
before_action :redirect_if_progress, except: :show before_action :redirect_if_progress, except: :show
def new def new
...@@ -24,21 +24,36 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -24,21 +24,36 @@ class Projects::ImportsController < Projects::ApplicationController
end end
def show def show
unless @project.import_in_progress? if @project.repository_exists? || @project.import_finished?
if @project.import_finished? if continue_params
redirect_to(project_path(@project)) and return redirect_to continue_params[:to], notice: continue_params[:notice]
else else
redirect_to(new_namespace_project_import_path(@project.namespace, redirect_to project_path(@project), notice: "The project was successfully forked."
@project)) and return
end end
elsif @project.import_failed?
redirect_to new_namespace_project_import_path(@project.namespace, @project)
else
if continue_params && continue_params[:notice_now]
flash.now[:notice] = continue_params[:notice_now]
end
# Render
end end
end end
private private
def continue_params
continue_params = params[:continue]
if continue_params
continue_params.permit(:to, :notice, :notice_now)
else
nil
end
end
def require_no_repo def require_no_repo
if @project.repository_exists? && !@project.import_in_progress? if @project.repository_exists? && !@project.import_in_progress?
redirect_to(namespace_project_path(@project.namespace, @project)) and return redirect_to(namespace_project_path(@project.namespace, @project))
end end
end end
......
...@@ -139,7 +139,6 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -139,7 +139,6 @@ class Projects::NotesController < Projects::ApplicationController
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
html: note_to_html(note), html: note_to_html(note),
award: note.is_award, award: note.is_award,
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
note: note.note, note: note.note,
discussion_html: note_to_discussion_html(note), discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note) discussion_with_diff_html: note_to_discussion_with_diff_html(note)
......
class Projects::ServicesController < Projects::ApplicationController class Projects::ServicesController < Projects::ApplicationController
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain, ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :drone_url, :build_type, :build_key, :server, :teamcity_url, :drone_url, :build_type,
...@@ -10,7 +10,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -10,7 +10,8 @@ class Projects::ServicesController < Projects::ApplicationController
:notify_only_broken_builds, :add_pusher, :notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification] :server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
:jira_issue_transition_id]
# Parameters to ignore if no value is specified # Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password] FILTER_BLANK_PARAMS = [:password]
......
# Controller for viewing a repository's file structure # Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController class Projects::TreeController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include CreatesMergeRequestForCommit include CreatesCommit
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :assign_dir_vars, only: [:create_dir] before_action :assign_dir_vars, only: [:create_dir]
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:create_dir] before_action :authorize_edit_tree!, only: [:create_dir]
def show def show
return render_404 unless @repository.commit(@ref) return render_404 unless @repository.commit(@ref)
...@@ -34,44 +34,20 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -34,44 +34,20 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir def create_dir
return render_404 unless @commit_params.values.all? return render_404 unless @commit_params.values.all?
begin create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
result = Files::CreateDirService.new(@project, current_user, @commit_params).execute success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)),
message = result[:message] failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
rescue => e
message = e.to_s
end
if result && result[:status] == :success
flash[:notice] = "The directory has been successfully created"
respond_to do |format|
format.html { redirect_to after_create_dir_path }
end
else
flash[:alert] = message
respond_to do |format|
format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, @new_branch) }
end
end
end end
private private
def assign_dir_vars def assign_dir_vars
@new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref @target_branch = params[:target_branch]
@dir_name = File.join(@path, params[:dir_name]) @dir_name = File.join(@path, params[:dir_name])
@commit_params = { @commit_params = {
file_path: @dir_name, file_path: @dir_name,
current_branch: @ref,
target_branch: @new_branch,
commit_message: params[:commit_message], commit_message: params[:commit_message],
} }
end end
def after_create_dir_path
if create_merge_request?
new_merge_request_path
else
namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name))
end
end
end end
...@@ -171,7 +171,7 @@ class ProjectsController < ApplicationController ...@@ -171,7 +171,7 @@ class ProjectsController < ApplicationController
@project.reload @project.reload
render json: { render json: {
html: view_to_html_string("projects/buttons/_star") star_count: @project.star_count
} }
end end
......
...@@ -50,5 +50,17 @@ module AuthHelper ...@@ -50,5 +50,17 @@ module AuthHelper
current_user.identities.exists?(provider: provider.to_s) current_user.identities.exists?(provider: provider.to_s)
end end
def two_factor_skippable?
current_application_settings.require_two_factor_authentication &&
!current_user.two_factor_enabled &&
current_application_settings.two_factor_grace_period &&
!two_factor_grace_period_expired?
end
def two_factor_grace_period_expired?
current_user.otp_grace_period_started_at &&
(current_user.otp_grace_period_started_at + current_application_settings.two_factor_grace_period.hours) < Time.current
end
extend self extend self
end end
...@@ -22,32 +22,90 @@ module BlobHelper ...@@ -22,32 +22,90 @@ module BlobHelper
%w(credits changelog news copying copyright license authors) %w(credits changelog news copying copyright license authors)
end end
def edit_blob_link(project, ref, path, options = {}) def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
blob = return unless current_user
begin
project.repository.blob_at(ref, path)
rescue
nil
end
return unless blob && blob.text? && blob_editable?(blob) blob = project.repository.blob_at(ref, path) rescue nil
return unless blob && blob_text_viewable?(blob)
text = 'Edit'
after = options[:after] || ''
from_mr = options[:from_merge_request_id] from_mr = options[:from_merge_request_id]
link_opts = {} link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr link_opts[:from_merge_request_id] = from_mr if from_mr
cls = 'btn btn-small'
link_to(text, edit_path = namespace_project_edit_blob_path(project.namespace, project,
namespace_project_edit_blob_path(project.namespace, project,
tree_join(ref, path), tree_join(ref, path),
link_opts), link_opts)
class: cls
) + after.html_safe if !on_top_of_branch?
button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob)
link_to "Edit", edit_path, class: 'btn btn-small'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: edit_path,
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
continue: continue_params)
link_to "Edit", fork_path, class: 'btn btn-small', method: :post
end
end
def modify_file_link(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:)
return unless current_user
blob = project.repository.blob_at(ref, path) rescue nil
return unless blob
if !on_top_of_branch?
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_edit_blob?(blob)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
notice_now: edit_in_new_fork_notice_now
}
fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
continue: continue_params)
link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post
end
end
def replace_blob_link(project = @project, ref = @ref, path = @path)
modify_file_link(
project,
ref,
path,
label: "Replace",
action: "replace",
btn_class: "default",
modal_type: "upload"
)
end
def delete_blob_link(project = @project, ref = @ref, path = @path)
modify_file_link(
project,
ref,
path,
label: "Delete",
action: "delete",
btn_class: "remove",
modal_type: "remove"
)
end end
def blob_editable?(blob, project = @project, ref = @ref) def can_edit_blob?(blob, project = @project, ref = @ref)
!blob.lfs_pointer? && allowed_tree_edit?(project, ref) !blob.lfs_pointer? && can_edit_tree?(project, ref)
end end
def leave_edit_message def leave_edit_message
...@@ -70,7 +128,7 @@ module BlobHelper ...@@ -70,7 +128,7 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw") icon("#{file_type_icon_class('file', mode, name)} fw")
end end
def blob_viewable?(blob) def blob_text_viewable?(blob)
blob && blob.text? && !blob.lfs_pointer? blob && blob.text? && !blob.lfs_pointer?
end end
......
...@@ -94,11 +94,14 @@ module IssuesHelper ...@@ -94,11 +94,14 @@ module IssuesHelper
end.sort.to_sentence(last_word_connector: ', or ') end.sort.to_sentence(last_word_connector: ', or ')
end end
def url_to_emoji(name) def emoji_icon(name, unicode = nil, aliases = [])
emoji_path = ::AwardEmoji.path_to_emoji_image(name) unicode ||= Emoji.emoji_filename(name)
url_to_image(emoji_path)
rescue StandardError content_tag :div, "",
"" class: "icon emoji-icon emoji-#{unicode}",
"data-emoji" => name,
"data-aliases" => aliases.join(" "),
"data-unicode-name" => unicode
end end
def emoji_author_list(notes, current_user) def emoji_author_list(notes, current_user)
...@@ -109,10 +112,6 @@ module IssuesHelper ...@@ -109,10 +112,6 @@ module IssuesHelper
list.join(", ") list.join(", ")
end end
def emoji_list
::AwardEmoji::EMOJI_LIST
end
def note_active_class(notes, current_user) def note_active_class(notes, current_user)
if current_user && notes.pluck(:author_id).include?(current_user.id) if current_user && notes.pluck(:author_id).include?(current_user.id)
"active" "active"
......
...@@ -27,7 +27,16 @@ module MergeRequestsHelper ...@@ -27,7 +27,16 @@ module MergeRequestsHelper
end end
def ci_build_details_path(merge_request) def ci_build_details_path(merge_request)
merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch)
return nil unless build_url
parsed_url = URI.parse(build_url)
unless parsed_url.userinfo.blank?
parsed_url.userinfo = ''
end
parsed_url.to_s
end end
def merge_path_description(merge_request, separator) def merge_path_description(merge_request, separator)
......
...@@ -105,6 +105,14 @@ module ProjectsHelper ...@@ -105,6 +105,14 @@ module ProjectsHelper
end end
end end
def user_max_access_in_project(user_id, project)
level = project.team.max_member_access(user_id)
if level
Gitlab::Access.options_with_owner.key(level)
end
end
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
...@@ -277,14 +285,6 @@ module ProjectsHelper ...@@ -277,14 +285,6 @@ module ProjectsHelper
end end
end end
def user_max_access_in_project(user, project)
level = project.team.max_member_access(user)
if level
Gitlab::Access.options_with_owner.key(level)
end
end
def leave_project_message(project) def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?" "Are you sure you want to leave \"#{project.name}\" project?"
end end
......
...@@ -50,22 +50,47 @@ module TreeHelper ...@@ -50,22 +50,47 @@ module TreeHelper
project.repository.branch_names.include?(ref) project.repository.branch_names.include?(ref)
end end
def allowed_tree_edit?(project = nil, ref = nil) def can_edit_tree?(project = nil, ref = nil)
project ||= @project project ||= @project
ref ||= @ref ref ||= @ref
return false unless on_top_of_branch?(project, ref) return false unless on_top_of_branch?(project, ref)
can?(current_user, :push_code, project) can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project))
end end
def tree_edit_branch(project = @project, ref = @ref) def tree_edit_branch(project = @project, ref = @ref)
if allowed_tree_edit?(project, ref) return unless can_edit_tree?(project, ref)
if can_push_branch?(project, ref) if can_push_branch?(project, ref)
ref ref
else else
project = tree_edit_project(project)
project.repository.next_patch_branch project.repository.next_patch_branch
end end
end end
def tree_edit_project(project = @project)
if can?(current_user, :push_code, project)
project
elsif current_user && current_user.already_forked?(project)
current_user.fork_of(project)
end
end
def edit_in_new_fork_notice_now
"You're not allowed to make changes to this project directly." +
" A fork of this project is being created that you can make changes in, so you can submit a merge request."
end
def edit_in_new_fork_notice
"You're not allowed to make changes to this project directly." +
" A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
def commit_in_fork_help
"A new branch will be created in your fork and a new merge request will be started."
end end
def tree_breadcrumbs(tree, max_links = 2) def tree_breadcrumbs(tree, max_links = 2)
......
...@@ -69,7 +69,6 @@ module VisibilityLevelHelper ...@@ -69,7 +69,6 @@ module VisibilityLevelHelper
def skip_level?(form_model, level) def skip_level?(form_model, level)
form_model.is_a?(Project) && form_model.is_a?(Project) &&
form_model.forked? && !form_model.visibility_level_allowed?(level)
!Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
end end
end end
...@@ -132,14 +132,14 @@ class Ability ...@@ -132,14 +132,14 @@ class Ability
end end
def public_project_rules def public_project_rules
project_guest_rules + [ @public_project_rules ||= project_guest_rules + [
:download_code, :download_code,
:fork_project :fork_project
] ]
end end
def project_guest_rules def project_guest_rules
[ @project_guest_rules ||= [
:read_project, :read_project,
:read_wiki, :read_wiki,
:read_issue, :read_issue,
...@@ -157,7 +157,7 @@ class Ability ...@@ -157,7 +157,7 @@ class Ability
end end
def project_report_rules def project_report_rules
project_guest_rules + [ @project_report_rules ||= project_guest_rules + [
:create_commit_status, :create_commit_status,
:read_commit_statuses, :read_commit_statuses,
:download_code, :download_code,
...@@ -170,7 +170,7 @@ class Ability ...@@ -170,7 +170,7 @@ class Ability
end end
def project_dev_rules def project_dev_rules
project_report_rules + [ @project_dev_rules ||= project_report_rules + [
:admin_merge_request, :admin_merge_request,
:create_merge_request, :create_merge_request,
:create_wiki, :create_wiki,
...@@ -181,7 +181,7 @@ class Ability ...@@ -181,7 +181,7 @@ class Ability
end end
def project_archived_rules def project_archived_rules
[ @project_archived_rules ||= [
:create_merge_request, :create_merge_request,
:push_code, :push_code,
:push_code_to_protected_branches, :push_code_to_protected_branches,
...@@ -191,7 +191,7 @@ class Ability ...@@ -191,7 +191,7 @@ class Ability
end end
def project_master_rules def project_master_rules
project_dev_rules + [ @project_master_rules ||= project_dev_rules + [
:push_code_to_protected_branches, :push_code_to_protected_branches,
:update_project_snippet, :update_project_snippet,
:update_merge_request, :update_merge_request,
...@@ -206,7 +206,7 @@ class Ability ...@@ -206,7 +206,7 @@ class Ability
end end
def project_admin_rules def project_admin_rules
project_master_rules + [ @project_admin_rules ||= project_master_rules + [
:change_namespace, :change_namespace,
:change_visibility_level, :change_visibility_level,
:rename_project, :rename_project,
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
# shared_runners_enabled :boolean default(TRUE), not null # shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null # max_artifacts_size :integer default(100), not null
# runners_registration_token :string(255) # runners_registration_token :string(255)
# require_two_factor_authentication :boolean default(TRUE)
# two_factor_grace_period :integer default(48)
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
...@@ -58,6 +60,9 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -58,6 +60,9 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true, allow_blank: true,
email: true email: true
validates :two_factor_grace_period,
numericality: { greater_than_or_equal_to: 0 }
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
...@@ -112,6 +117,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -112,6 +117,8 @@ class ApplicationSetting < ActiveRecord::Base
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'], import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
two_factor_grace_period: 48
) )
end end
...@@ -134,4 +141,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -134,4 +141,8 @@ class ApplicationSetting < ActiveRecord::Base
/x) /x)
self.restricted_signup_domains.reject! { |d| d.empty? } self.restricted_signup_domains.reject! { |d| d.empty? }
end end
def runners_registration_token
ensure_runners_registration_token!
end
end end
...@@ -135,6 +135,16 @@ module Ci ...@@ -135,6 +135,16 @@ module Ci
predefined_variables + yaml_variables + project_variables + trigger_variables predefined_variables + yaml_variables + project_variables + trigger_variables
end end
def merge_request
merge_requests = MergeRequest.includes(:merge_request_diff)
.where(source_branch: ref, source_project_id: commit.gl_project_id)
.reorder(iid: :asc)
merge_requests.find do |merge_request|
merge_request.commits.any? { |ci| ci.id == commit.sha }
end
end
def project def project
commit.project commit.project
end end
...@@ -170,7 +180,8 @@ module Ci ...@@ -170,7 +180,8 @@ module Ci
def extract_coverage(text, regex) def extract_coverage(text, regex)
begin begin
matches = text.gsub(Regexp.new(regex)).to_a.last matches = text.scan(Regexp.new(regex)).last
matches = matches.last if matches.kind_of?(Array)
coverage = matches.gsub(/\d+(\.\d+)?/).first coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present? if coverage.present?
......
...@@ -37,7 +37,7 @@ module Participable ...@@ -37,7 +37,7 @@ module Participable
# Be aware that this method makes a lot of sql queries. # Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request # Save result into variable if you are going to reuse it inside same request
def participants(current_user = self.author, load_lazy_references: true) def participants(current_user = self.author)
participants = participants =
Gitlab::ReferenceExtractor.lazily do Gitlab::ReferenceExtractor.lazily do
self.class.participant_attrs.flat_map do |attr| self.class.participant_attrs.flat_map do |attr|
......
...@@ -18,15 +18,16 @@ module TokenAuthenticatable ...@@ -18,15 +18,16 @@ module TokenAuthenticatable
define_method("ensure_#{token_field}") do define_method("ensure_#{token_field}") do
current_token = read_attribute(token_field) current_token = read_attribute(token_field)
if current_token.blank? current_token.blank? ? write_new_token(token_field) : current_token
write_attribute(token_field, generate_token_for(token_field))
else
current_token
end end
define_method("ensure_#{token_field}!") do
send("reset_#{token_field}!") if read_attribute(token_field).blank?
read_attribute(token_field)
end end
define_method("reset_#{token_field}!") do define_method("reset_#{token_field}!") do
write_attribute(token_field, generate_token_for(token_field)) write_new_token(token_field)
save! save!
end end
end end
...@@ -34,7 +35,12 @@ module TokenAuthenticatable ...@@ -34,7 +35,12 @@ module TokenAuthenticatable
private private
def generate_token_for(token_field) def write_new_token(token_field)
new_token = generate_token(token_field)
write_attribute(token_field, new_token)
end
def generate_token(token_field)
loop do loop do
token = Devise.friendly_token token = Devise.friendly_token
break token unless self.class.unscoped.find_by(token_field => token) break token unless self.class.unscoped.find_by(token_field => token)
......
...@@ -16,7 +16,7 @@ class GlobalMilestone ...@@ -16,7 +16,7 @@ class GlobalMilestone
end end
def safe_title def safe_title
@title.to_slug.to_s @title.to_slug.normalize.to_s
end end
def expired? def expired?
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
class Identity < ActiveRecord::Base class Identity < ActiveRecord::Base
include Sortable include Sortable
include CaseSensitivity
belongs_to :user belongs_to :user
validates :provider, presence: true validates :provider, presence: true
......
...@@ -86,7 +86,7 @@ class Issue < ActiveRecord::Base ...@@ -86,7 +86,7 @@ class Issue < ActiveRecord::Base
def referenced_merge_requests def referenced_merge_requests
Gitlab::ReferenceExtractor.lazily do Gitlab::ReferenceExtractor.lazily do
[self, *notes].flat_map do |note| [self, *notes].flat_map do |note|
note.all_references(load_lazy_references: false).merge_requests note.all_references.merge_requests
end end
end.sort_by(&:iid) end.sort_by(&:iid)
end end
......
class JiraIssue < ExternalIssue
end
...@@ -335,7 +335,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -335,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) } issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description)) closed_by_message(description))
issues.uniq issues.uniq(&:id)
else else
[] []
end end
......
...@@ -64,6 +64,19 @@ class Project < ActiveRecord::Base ...@@ -64,6 +64,19 @@ class Project < ActiveRecord::Base
update_column(:last_activity_at, self.created_at) update_column(:last_activity_at, self.created_at)
end end
# update visibility_levet of forks
after_update :update_forks_visibility_level
def update_forks_visibility_level
return unless visibility_level < visibility_level_was
forks.each do |forked_project|
if forked_project.visibility_level > visibility_level
forked_project.visibility_level = visibility_level
forked_project.save!
end
end
end
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags acts_as_taggable_on :tags
...@@ -101,8 +114,11 @@ class Project < ActiveRecord::Base ...@@ -101,8 +114,11 @@ class Project < ActiveRecord::Base
has_one :external_wiki_service, dependent: :destroy has_one :external_wiki_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
has_many :forked_project_links, foreign_key: "forked_from_project_id"
has_many :forks, through: :forked_project_links, source: :forked_to_project
# Merge Requests for target project should be removed with it # Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
# Merge requests from source project should be kept when source project was removed # Merge requests from source project should be kept when source project was removed
...@@ -499,6 +515,10 @@ class Project < ActiveRecord::Base ...@@ -499,6 +515,10 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.find(&:activated?) @ci_service ||= ci_services.find(&:activated?)
end end
def jira_tracker?
issues_tracker.to_param == 'jira'
end
def avatar_type def avatar_type
unless self.avatar.image? unless self.avatar.image?
self.errors.add :avatar, 'only images allowed' self.errors.add :avatar, 'only images allowed'
...@@ -764,7 +784,7 @@ class Project < ActiveRecord::Base ...@@ -764,7 +784,7 @@ class Project < ActiveRecord::Base
end end
def forks_count def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count forks.count
end end
def find_label(name) def find_label(name)
...@@ -799,6 +819,10 @@ class Project < ActiveRecord::Base ...@@ -799,6 +819,10 @@ class Project < ActiveRecord::Base
false false
end end
def jira_tracker_active?
jira_tracker? && jira_service.active
end
def ci_commit(sha) def ci_commit(sha)
ci_commits.find_by(sha: sha) ci_commits.find_by(sha: sha)
end end
...@@ -854,4 +878,9 @@ class Project < ActiveRecord::Base ...@@ -854,4 +878,9 @@ class Project < ActiveRecord::Base
def open_issues_count def open_issues_count
issues.opened.count issues.opened.count
end end
def visibility_level_allowed?(level)
return true unless forked?
Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
end
end end
...@@ -18,6 +18,11 @@ ...@@ -18,6 +18,11 @@
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# #
# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
class GitlabCiService < CiService class GitlabCiService < CiService
# this is no longer used # We override the active accessor to always make GitLabCiService disabled
# Otherwise the GitLabCiService can be picked, but should never be since it's deprecated
def active
false
end
end end
...@@ -19,9 +19,24 @@ ...@@ -19,9 +19,24 @@
# #
class JiraService < IssueTrackerService class JiraService < IssueTrackerService
include HTTParty
include Gitlab::Application.routes.url_helpers include Gitlab::Application.routes.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url DEFAULT_API_VERSION = 2
prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
:title, :description, :project_url, :issues_url, :new_issue_url
before_validation :set_api_url, :set_jira_issue_transition_id
before_update :reset_password
def reset_password
# don't reset the password if a new one is provided
if api_url_changed? && !password_touched?
self.password = nil
end
end
def help def help
line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\ line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\
...@@ -54,4 +69,228 @@ class JiraService < IssueTrackerService ...@@ -54,4 +69,228 @@ class JiraService < IssueTrackerService
def to_param def to_param
'jira' 'jira'
end end
def fields
super.push(
{ type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' },
{ type: 'text', name: 'username', placeholder: '' },
{ type: 'password', name: 'password', placeholder: '' },
{ type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
)
end
def execute(push, issue = nil)
if issue.nil?
# No specific issue, that means
# we just want to test settings
test_settings
else
close_issue(push, issue)
end
end
def create_cross_reference_note(mentioned, noteable, author)
issue_name = mentioned.id
project = self.project
noteable_name = noteable.class.name.underscore.downcase
noteable_id = if noteable.is_a?(Commit)
noteable.id
else
noteable.iid
end
entity_url = build_entity_url(noteable_name.to_sym, noteable_id)
data = {
user: {
name: author.name,
url: resource_url(user_path(author)),
},
project: {
name: project.path_with_namespace,
url: resource_url(namespace_project_path(project.namespace, project))
},
entity: {
name: noteable_name.humanize.downcase,
url: entity_url
}
}
add_comment(data, issue_name)
end
def test_settings
result = JiraService.get(
jira_api_test_url,
headers: {
'Content-Type' => 'application/json',
'Authorization' => "Basic #{auth}"
}
)
case result.code
when 201, 200
Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.")
true
else
Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}")
false
end
rescue Errno::ECONNREFUSED => e
Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}."
false
end
private
def build_api_url_from_project_url
server = URI(project_url)
default_ports = [["http",80],["https",443]].include?([server.scheme,server.port])
server_url = "#{server.scheme}://#{server.host}"
server_url.concat(":#{server.port}") unless default_ports
"#{server_url}/rest/api/#{DEFAULT_API_VERSION}"
rescue
"" # looks like project URL was not valid
end
def set_api_url
self.api_url = build_api_url_from_project_url if self.api_url.blank?
end
def set_jira_issue_transition_id
self.jira_issue_transition_id ||= "2"
end
def close_issue(entity, issue)
commit_id = if entity.is_a?(Commit)
entity.id
elsif entity.is_a?(MergeRequest)
entity.last_commit.id
end
commit_url = build_entity_url(:commit, commit_id)
# Depending on the JIRA project's workflow, a comment during transition
# may or may not be allowed. Split the operation in to two calls so the
# comment always works.
transition_issue(issue)
add_issue_solved_comment(issue, commit_id, commit_url)
end
def transition_issue(issue)
message = {
transition: {
id: jira_issue_transition_id
}
}
send_message(close_issue_url(issue.iid), message.to_json)
end
def add_issue_solved_comment(issue, commit_id, commit_url)
comment = {
body: "Issue solved with [#{commit_id}|#{commit_url}]."
}
send_message(comment_url(issue.iid), comment.to_json)
end
def add_comment(data, issue_name)
url = comment_url(issue_name)
user_name = data[:user][:name]
user_url = data[:user][:url]
entity_name = data[:entity][:name]
entity_url = data[:entity][:url]
project_name = data[:project][:name]
message = {
body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]."
}
unless existing_comment?(issue_name, message[:body])
send_message(url, message.to_json)
end
end
def auth
require 'base64'
Base64.urlsafe_encode64("#{self.username}:#{self.password}")
end
def send_message(url, message)
result = JiraService.post(
url,
body: message,
headers: {
'Content-Type' => 'application/json',
'Authorization' => "Basic #{auth}"
}
)
message = case result.code
when 201, 200, 204
"#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}."
when 401
"#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again."
else
"#{self.class.name} ERROR #{result.code}: #{result.parsed_response}"
end
Rails.logger.info(message)
message
rescue URI::InvalidURIError, Errno::ECONNREFUSED => e
Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}."
end
def existing_comment?(issue_name, new_comment)
result = JiraService.get(
comment_url(issue_name),
headers: {
'Content-Type' => 'application/json',
'Authorization' => "Basic #{auth}"
}
)
case result.code
when 201, 200
existing_comments = JSON.parse(result.body)['comments']
if existing_comments.present?
return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any?
end
end
false
rescue JSON::ParserError
false
end
def resource_url(resource)
"#{Settings.gitlab['url'].chomp("/")}#{resource}"
end
def build_entity_url(entity_name, entity_id)
resource_url(
polymorphic_url(
[
self.project.namespace.becomes(Namespace),
self.project,
entity_name
],
id: entity_id,
routing_type: :path
)
)
end
def close_issue_url(issue_name)
"#{self.api_url}/issue/#{issue_name}/transitions"
end
def comment_url(issue_name)
"#{self.api_url}/issue/#{issue_name}/comment"
end
def jira_api_test_url
"#{self.api_url}/myself"
end
end end
...@@ -592,20 +592,30 @@ class Repository ...@@ -592,20 +592,30 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo) Gitlab::Popen.popen(args, path_to_repo)
end end
def with_tmp_ref(oldrev = nil)
random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head"
if oldrev && !Gitlab::Git.blank_ref?(oldrev)
rugged.references.create(tmp_ref, oldrev)
end
# Make commit in tmp ref
yield(tmp_ref)
ensure
rugged.references.delete(tmp_ref) rescue nil
end
def commit_with_hooks(current_user, branch) def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
was_empty = empty? was_empty = empty?
# Create temporary ref
random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head"
unless was_empty unless was_empty
oldrev = find_branch(branch).target oldrev = find_branch(branch).target
rugged.references.create(tmp_ref, oldrev)
end end
with_tmp_ref(oldrev) do |tmp_ref|
# Make commit in tmp ref # Make commit in tmp ref
newrev = yield(tmp_ref) newrev = yield(tmp_ref)
...@@ -629,10 +639,7 @@ class Repository ...@@ -629,10 +639,7 @@ class Repository
end end
end end
end end
rescue GitHooksService::PreReceiveError end
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
raise
end end
private private
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
# bio :string(255) # bio :string(255)
# failed_attempts :integer default(0) # failed_attempts :integer default(0)
# locked_at :datetime # locked_at :datetime
# unlock_token :string(255)
# username :string(255) # username :string(255)
# can_create_group :boolean default(TRUE), not null # can_create_group :boolean default(TRUE), not null
# can_create_team :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null
......
...@@ -39,10 +39,7 @@ class BaseService ...@@ -39,10 +39,7 @@ class BaseService
def deny_visibility_level(model, denied_visibility_level = nil) def deny_visibility_level(model, denied_visibility_level = nil)
denied_visibility_level ||= model.visibility_level denied_visibility_level ||= model.visibility_level
level_name = 'Unknown' level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level)
Gitlab::VisibilityLevel.options.each do |name, level|
level_name = name if level == denied_visibility_level
end
model.errors.add( model.errors.add(
:visibility_level, :visibility_level,
......
require_relative 'base_service' require_relative 'base_service'
class CreateBranchService < BaseService class CreateBranchService < BaseService
def execute(branch_name, ref) def execute(branch_name, ref, source_project: @project)
valid_branch = Gitlab::GitRefValidator.validate(branch_name) valid_branch = Gitlab::GitRefValidator.validate(branch_name)
if valid_branch == false if valid_branch == false
return error('Branch name invalid') return error('Branch name is invalid')
end end
repository = project.repository repository = project.repository
...@@ -13,7 +13,20 @@ class CreateBranchService < BaseService ...@@ -13,7 +13,20 @@ class CreateBranchService < BaseService
return error('Branch already exists') return error('Branch already exists')
end end
new_branch = nil
if source_project != @project
repository.with_tmp_ref do |tmp_ref|
repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{ref}",
tmp_ref
)
new_branch = repository.add_branch(current_user, branch_name, tmp_ref)
end
else
new_branch = repository.add_branch(current_user, branch_name, ref) new_branch = repository.add_branch(current_user, branch_name, ref)
end
if new_branch if new_branch
push_data = build_push_data(project, current_user, new_branch) push_data = build_push_data(project, current_user, new_branch)
......
...@@ -3,8 +3,10 @@ module Files ...@@ -3,8 +3,10 @@ module Files
class ValidationError < StandardError; end class ValidationError < StandardError; end
def execute def execute
@current_branch = params[:current_branch] @source_project = params[:source_project] || @project
@source_branch = params[:source_branch]
@target_branch = params[:target_branch] @target_branch = params[:target_branch]
@commit_message = params[:commit_message] @commit_message = params[:commit_message]
@file_path = params[:file_path] @file_path = params[:file_path]
@file_content = if params[:file_content_encoding] == 'base64' @file_content = if params[:file_content_encoding] == 'base64'
...@@ -16,8 +18,8 @@ module Files ...@@ -16,8 +18,8 @@ module Files
# Validate parameters # Validate parameters
validate validate
# Create new branch if it different from current_branch # Create new branch if it different from source_branch
if @target_branch != @current_branch if different_branch?
create_target_branch create_target_branch
end end
...@@ -26,18 +28,14 @@ module Files ...@@ -26,18 +28,14 @@ module Files
else else
error("Something went wrong. Your changes were not committed") error("Something went wrong. Your changes were not committed")
end end
rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message) error(ex.message)
end end
private private
def current_branch def different_branch?
@current_branch ||= params[:current_branch] @source_branch != @target_branch || @source_project != @project
end
def target_branch
@target_branch ||= params[:target_branch]
end end
def raise_error(message) def raise_error(message)
...@@ -52,11 +50,11 @@ module Files ...@@ -52,11 +50,11 @@ module Files
end end
unless project.empty_repo? unless project.empty_repo?
unless repository.branch_names.include?(@current_branch) unless @source_project.repository.branch_names.include?(@source_branch)
raise_error("You can only create or edit files when you are on a branch") raise_error("You can only create or edit files when you are on a branch")
end end
if @current_branch != @target_branch if different_branch?
if repository.branch_names.include?(@target_branch) if repository.branch_names.include?(@target_branch)
raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes") raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
end end
...@@ -65,10 +63,10 @@ module Files ...@@ -65,10 +63,10 @@ module Files
end end
def create_target_branch def create_target_branch
result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch) result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project)
unless result[:status] == :success unless result[:status] == :success
raise_error("Something went wrong when we tried to create #{@target_branch} for you") raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
end end
end end
end end
......
...@@ -26,7 +26,7 @@ module Files ...@@ -26,7 +26,7 @@ module Files
unless project.empty_repo? unless project.empty_repo?
@file_path.slice!(0) if @file_path.start_with?('/') @file_path.slice!(0) if @file_path.start_with?('/')
blob = repository.blob_at_branch(@current_branch, @file_path) blob = repository.blob_at_branch(@source_branch, @file_path)
if blob if blob
raise_error("Your changes could not be committed because a file with the same name already exists") raise_error("Your changes could not be committed because a file with the same name already exists")
......
module Issues module Issues
class CloseService < Issues::BaseService class CloseService < Issues::BaseService
def execute(issue, commit = nil) def execute(issue, commit = nil)
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(commit, issue)
return issue
end
if project.default_issues_tracker? && issue.close if project.default_issues_tracker? && issue.close
event_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user)
create_note(issue, commit) create_note(issue, commit)
......
...@@ -3,7 +3,8 @@ module Projects ...@@ -3,7 +3,8 @@ module Projects
def execute def execute
# check that user is allowed to set specified visibility_level # check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level] new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != project.visibility_level if new_visibility
if new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) && unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(project, new_visibility) deny_visibility_level(project, new_visibility)
...@@ -11,6 +12,9 @@ module Projects ...@@ -11,6 +12,9 @@ module Projects
end end
end end
return false unless visibility_level_allowed?(new_visibility)
end
new_branch = params[:default_branch] new_branch = params[:default_branch]
if project.repository.exists? && new_branch && new_branch != project.default_branch if project.repository.exists? && new_branch && new_branch != project.default_branch
...@@ -23,5 +27,19 @@ module Projects ...@@ -23,5 +27,19 @@ module Projects
end end
end end
end end
private
def visibility_level_allowed?(level)
return true if project.visibility_level_allowed?(level)
level_name = Gitlab::VisibilityLevel.level_name(level)
project.errors.add(
:visibility_level,
"#{level_name} could not be set as visibility level of this project - parent project settings are more restrictive"
)
false
end
end end
end end
...@@ -241,8 +241,13 @@ class SystemNoteService ...@@ -241,8 +241,13 @@ class SystemNoteService
note_options.merge!(noteable: noteable) note_options.merge!(noteable: noteable)
end end
if noteable.is_a?(ExternalIssue)
noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
else
create_note(note_options) create_note(note_options)
end end
end
def self.cross_reference?(note_text) def self.cross_reference?(note_text)
note_text.start_with?(cross_reference_note_prefix) note_text.start_with?(cross_reference_note_prefix)
...@@ -259,7 +264,7 @@ class SystemNoteService ...@@ -259,7 +264,7 @@ class SystemNoteService
# #
# Returns Boolean # Returns Boolean
def self.cross_reference_disallowed?(noteable, mentioner) def self.cross_reference_disallowed?(noteable, mentioner)
return true if noteable.is_a?(ExternalIssue) return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active?
return false unless mentioner.is_a?(MergeRequest) return false unless mentioner.is_a?(MergeRequest)
return false unless noteable.is_a?(Commit) return false unless noteable.is_a?(Commit)
......
...@@ -104,6 +104,18 @@ ...@@ -104,6 +104,18 @@
= f.label :signin_enabled do = f.label :signin_enabled do
= f.check_box :signin_enabled = f.check_box :signin_enabled
Sign-in enabled Sign-in enabled
.form-group
= f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
Require all users to setup Two-Factor authentication
.form-group
= f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
.form-group .form-group
= f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2' = f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
...@@ -79,6 +79,10 @@ ...@@ -79,6 +79,10 @@
GitLab API GitLab API
%span.pull-right %span.pull-right
= API::API::version = API::API::version
%p
Git
%span.pull-right
= Gitlab::Git.version
%p %p
Ruby Ruby
%span.pull-right %span.pull-right
......
- page_title "Identities", @user.name, "Users" - page_title "Identities", @user.name, "Users"
= render 'admin/users/head' = render 'admin/users/head'
= link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new'
- if @identities.present? - if @identities.present?
.table-holder .table-holder
%table.table %table.table
......
- page_title "New Identity"
%h3.page-title New identity
%hr
= render 'form'
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
To register a new runner you should enter the following registration token. To register a new runner you should enter the following registration token.
With this token the runner will request a unique runner token and use that for future communication. With this token the runner will request a unique runner token and use that for future communication.
Registration token is Registration token is
%code{ id: 'runners-token' } #{current_application_settings.ensure_runners_registration_token} %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token}
.bs-callout.clearfix .bs-callout.clearfix
.pull-left .pull-left
......
...@@ -41,5 +41,3 @@ ...@@ -41,5 +41,3 @@
%i.fa.fa-remove.incorrect-syntax %i.fa.fa-remove.incorrect-syntax
%b Error: %b Error:
= @error = @error
:plain
$(".results").html("#{escape_javascript(render "create")}")
\ No newline at end of file
%h2 Check your .gitlab-ci.yml %h2 Check your .gitlab-ci.yml
%hr %hr
= form_tag ci_lint_path, method: :post, remote: true do .row
.control-group = form_tag ci_lint_path, method: :post do
= label_tag :content, "Content of .gitlab-ci.yml", class: 'control-label' .form-group
.controls = label_tag :content, 'Content of .gitlab-ci.yml', class: 'control-label text-nowrap'
.col-sm-12
= text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
.col-sm-12
.pull-left.prepend-top-10
= submit_tag 'Validate', class: 'btn btn-success submit-yml'
.control-group.clearfix .row.prepend-top-20
.controls.pull-left.prepend-top-10 .col-sm-12
= submit_tag "Validate", class: 'btn btn-success submit-yml' .results
= render partial: 'create' if defined?(@status)
%p.text-center.loading
%i.fa.fa-refresh.fa-spin
.results.prepend-top-20
:javascript
$(".loading").hide();
$('form').bind('ajax:beforeSend', function() {
$(".loading").show();
});
$('form').bind('ajax:complete', function() {
$(".loading").hide();
});
= content_for :flash_message do = content_for :flash_message do
= render 'shared/project_limit' = render 'shared/project_limit'
.top-area
%ul.center-top-menu %ul.left-top-menu
= nav_link(page: [dashboard_projects_path, root_path]) do = nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects Your Projects
...@@ -11,3 +11,10 @@ ...@@ -11,3 +11,10 @@
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore Projects Explore Projects
.projects-search-form
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-green' do
%i.fa.fa-plus
New Project
.projects-list-holder .projects-list-holder
.projects-search-form
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if current_user.can_create_project?
%span.input-group-btn
= link_to new_project_path, class: 'btn btn-green' do
%i.fa.fa-plus
New Project
= render 'shared/projects/list', projects: @projects, ci: true = render 'shared/projects/list', projects: @projects, ci: true
<p>Hello <%= @resource.email %>!</p>
<p>Your account has been locked due to an excessive amount of unsuccessful sign in attempts.</p>
<p>Click the link below to unlock your account:</p>
<p><%= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) %></p>
%p
Hello #{@resource.name}!
%p
Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in
= time_ago_in_words(Devise.unlock_in.from_now)
or you may click the link below to unlock now.
%p= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token)
<h2>Resend unlock instructions</h2>
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.submit "Resend unlock instructions" %></div>
<% end %>
<%= render partial: "devise/shared/links" %>
.login-box
.login-heading
%h3 Resend unlock email
.login-body
= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f|
.devise-errors
= devise_error_messages!
.clearfix.append-bottom-20
= f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off'
.clearfix
= f.submit 'Resend unlock instructions', class: 'btn btn-success'
.clearfix.prepend-top-20
= render 'devise/shared/sign_in_link'
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- else - else
= render 'explore/head' = render 'explore/head'
.gray-content-block.clearfix .gray-content-block.clearfix.second-block
= render 'filter' = render 'filter'
= render 'projects', projects: @projects = render 'projects', projects: @projects
= paginate @projects, theme: "gitlab" = paginate @projects, theme: "gitlab"
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= render 'explore/head' = render 'explore/head'
.explore-trending-block .explore-trending-block
.gray-content-block .gray-content-block.second-block
.pull-right .pull-right
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
.oneline .oneline
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= render 'explore/head' = render 'explore/head'
.explore-trending-block .explore-trending-block
.gray-content-block .gray-content-block.second-block
.pull-right .pull-right
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
.oneline .oneline
......
.panel.panel-default.projects-list-holder .projects-list-holder
.panel-heading.clearfix .projects-search-form
.input-group .input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group - if can? current_user, :create_projects, @group
......
...@@ -5,24 +5,33 @@ ...@@ -5,24 +5,33 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
.dashboard .cover-block
.header-with-avatar.clearfix .avatar-holder
= link_to group_icon(@group), target: '_blank' do
= image_tag group_icon(@group), class: "avatar group-avatar s90" = image_tag group_icon(@group), class: "avatar group-avatar s90"
%h3 .cover-title
= @group.name = @group.name
.username
.cover-desc.username
@#{@group.path} @#{@group.path}
- if @group.description.present? - if @group.description.present?
.description .cover-desc.description
= markdown(@group.description, pipeline: :description) = markdown(@group.description, pipeline: :description)
%hr
= render 'shared/show_aside' - if can?(current_user, :read_group, @group)
%ul.center-top-menu.no-top
%li.active
= link_to "#activity", 'data-toggle' => 'tab' do
Activity
- if @projects.present?
%li
= link_to "#projects", 'data-toggle' => 'tab' do
Projects
- if can?(current_user, :read_group, @group) .tab-content
.row .tab-pane.active#activity
%section.activities.col-md-7 .gray-content-block.activity-filter-block
.hidden-xs
- if current_user - if current_user
= render "events/event_last_push", event: @last_push = render "events/event_last_push", event: @last_push
.pull-right .pull-right
...@@ -30,12 +39,13 @@ ...@@ -30,12 +39,13 @@
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter' = render 'shared/event_filter'
%hr
.content_list .content_list
= spinner = spinner
%aside.side.col-md-5
.tab-pane#projects
= render "projects", projects: @projects = render "projects", projects: @projects
- else
- else
%p %p
This group does not have public projects This group does not have public projects
...@@ -12,6 +12,6 @@ ...@@ -12,6 +12,6 @@
comment = val.match(/^\S+ \S+ (.+)\n?$/); comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){ if( comment && comment.length > 1 && title.val() == '' ){
$('#key_title').val( comment[1] ); $('#key_title').val( comment[1] ).change();
} }
}); });
...@@ -38,3 +38,4 @@ ...@@ -38,3 +38,4 @@
= text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true = text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true
.form-actions .form-actions
= submit_tag 'Submit', class: 'btn btn-success' = submit_tag 'Submit', class: 'btn btn-success'
= link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch, class: 'btn btn-cancel' if two_factor_skippable?
...@@ -2,3 +2,7 @@ ...@@ -2,3 +2,7 @@
= button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create' = button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path, = link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message} class: 'btn btn-cancel', data: {confirm: leave_edit_message}
- unless can?(current_user, :push_code, @project)
.inline.prepend-left-10
= commit_in_fork_help
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
= icon('rss') = icon('rss')
.project-repo-buttons .project-repo-buttons
.split-one .split-one.count-buttons
= render 'projects/buttons/star' = render 'projects/buttons/star'
= render 'projects/buttons/fork' = render 'projects/buttons/fork'
...@@ -38,3 +38,6 @@ ...@@ -38,3 +38,6 @@
= render 'projects/buttons/dropdown' = render 'projects/buttons/dropdown'
= render 'projects/buttons/notifications' = render 'projects/buttons/notifications'
:coffeescript
new Star()
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank' class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files -# only show normal/blame view links for text files
- if blob_viewable?(@blob) - if blob_text_viewable?(@blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id) - if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id), = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm' class: 'btn btn-sm'
...@@ -14,13 +14,8 @@ ...@@ -14,13 +14,8 @@
= link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
tree_join(@commit.sha, @path)), class: 'btn btn-sm' tree_join(@commit.sha, @path)), class: 'btn btn-sm'
- if blob_editable?(@blob) - if current_user
.btn-group{ role: "group" } .btn-group{ role: "group" }
= edit_blob_link(@project, @ref, @path) = edit_blob_link
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace = replace_blob_link
%button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete = delete_blob_link
- elsif !on_top_of_branch?
.btn-group{ role: "group" }
%button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on a branch.", data: {container: 'body'}} Edit
%button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on a branch.", data: {container: 'body'}} Replace
%button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on a branch.", data: {container: 'body'}} Delete
...@@ -17,5 +17,9 @@ ...@@ -17,5 +17,9 @@
= submit_tag "Create directory", class: 'btn btn-create' = submit_tag "Create directory", class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
- unless can?(current_user, :push_code, @project)
.inline.prepend-left-10
= commit_in_fork_help
:javascript :javascript
new NewCommitForm($('.js-create-dir-form')) new NewCommitForm($('.js-create-dir-form'))
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment