Commit b6add255 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce_upstream' into 'master'

CE upstream

Unmerged paths:
  (use "git add <file>..." to mark resolution)

	both modified:   app/assets/stylesheets/pages/projects.scss
	both modified:   app/views/shared/_clone_panel.html.haml

@jschatz1 Please resolve these conflicts. 

See merge request !99
parents 8ad2f98b 7b24ea5c
...@@ -4,12 +4,28 @@ v 8.3.0 (unreleased) ...@@ -4,12 +4,28 @@ v 8.3.0 (unreleased)
- Merge when build succeeds (Zeger-Jan van de Weg) - Merge when build succeeds (Zeger-Jan van de Weg)
v 8.4.0 (unreleased) v 8.4.0 (unreleased)
- Implement new UI for group page - 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.
- Revert back upvote and downvote button to the issue and MR pages
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 v 8.3.0
- Add CAS support (tduehr)
- 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 link to merge request on build detail page.
- Add open_issues_count to project API (Stan Hu) - Add open_issues_count to project API (Stan Hu)
- Expand character set of usernames created by Omniauth (Corey Hinshaw) - Expand character set of usernames created by Omniauth (Corey Hinshaw)
- 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)
...@@ -70,7 +86,6 @@ v 8.3.0 ...@@ -70,7 +86,6 @@ v 8.3.0
- Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present
- Persist runners registration token in database - Persist runners registration token in database
- Fix online editor should not remove newlines at the end of the file - Fix online editor should not remove newlines at the end of the file
- Expose Git's version in the admin area
v 8.2.3 v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu) - Fix application settings cache not expiring after changes (Stan Hu)
......
...@@ -177,7 +177,7 @@ gem 'd3_rails', '~> 3.5.5' ...@@ -177,7 +177,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'
...@@ -195,7 +195,7 @@ gem 'mousetrap-rails', '~> 1.4.6' ...@@ -195,7 +195,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'
...@@ -207,9 +207,9 @@ gem 'font-awesome-rails', '~> 4.2' ...@@ -207,9 +207,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'
......
...@@ -377,15 +377,16 @@ GEM ...@@ -377,15 +377,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)
...@@ -652,12 +653,13 @@ GEM ...@@ -652,12 +653,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)
...@@ -772,7 +774,7 @@ GEM ...@@ -772,7 +774,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)
...@@ -886,10 +888,10 @@ DEPENDENCIES ...@@ -886,10 +888,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)
...@@ -943,7 +945,7 @@ DEPENDENCIES ...@@ -943,7 +945,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)
...@@ -972,7 +974,7 @@ DEPENDENCIES ...@@ -972,7 +974,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)
......
...@@ -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
...@@ -27,15 +43,19 @@ class @AwardsHandler ...@@ -27,15 +43,19 @@ class @AwardsHandler
decrementCounter: (emoji) -> decrementCounter: (emoji) ->
counter = @findEmojiIcon(emoji).siblings(".counter") counter = @findEmojiIcon(emoji).siblings(".counter")
emojiIcon = counter.parent()
if parseInt(counter.text()) > 1 if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1) counter.text(parseInt(counter.text()) - 1)
counter.parent().removeClass("active") emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji) @removeMeFromAuthorList(emoji)
else if emoji =="thumbsup" || emoji == "thumbsdown"
emojiIcon.tooltip("destroy")
counter.text(0)
emojiIcon.removeClass("active")
else else
award = counter.parent() emojiIcon.tooltip("destroy")
award.tooltip("destroy") emojiIcon.remove()
award.remove()
removeMeFromAuthorList: (emoji) -> removeMeFromAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent() award_block = @findEmojiIcon(emoji).parent()
...@@ -54,35 +74,39 @@ class @AwardsHandler ...@@ -54,35 +74,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 +114,7 @@ class @AwardsHandler ...@@ -90,7 +114,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 +123,43 @@ class @AwardsHandler ...@@ -99,3 +123,43 @@ 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(",")
_.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
......
@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 {
......
...@@ -383,7 +383,7 @@ table { ...@@ -383,7 +383,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;
...@@ -417,6 +417,11 @@ table { ...@@ -417,6 +417,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;
} }
......
...@@ -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;
......
...@@ -45,6 +45,10 @@ $blue-light: #2EA8E5; ...@@ -45,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;
......
...@@ -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,10 +91,9 @@ ...@@ -91,10 +91,9 @@
} }
} }
.input-group { .git-clone-holder {
display: inline-table; display: inline-table;
position: relative; position: relative;
top: 17px;
} }
.project-repo-buttons { .project-repo-buttons {
...@@ -102,11 +101,74 @@ ...@@ -102,11 +101,74 @@
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;
...@@ -339,6 +408,38 @@ ul.nav.nav-projects-tabs { ...@@ -339,6 +408,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;
...@@ -416,11 +517,18 @@ pre.light-well { ...@@ -416,11 +517,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,
......
...@@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base ...@@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base
before_action :validate_user_service_ticket! 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
...@@ -223,6 +224,12 @@ class ApplicationController < ActionController::Base ...@@ -223,6 +224,12 @@ class ApplicationController < ActionController::Base
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)
...@@ -363,6 +370,23 @@ class ApplicationController < ActionController::Base ...@@ -363,6 +370,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 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
...@@ -22,21 +22,36 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -22,21 +22,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)
......
# 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
...@@ -182,7 +182,7 @@ class ProjectsController < ApplicationController ...@@ -182,7 +182,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
......
module AppearancesHelper module AppearancesHelper
def brand_title def brand_title
if brand_item if brand_item && brand_item.title
brand_item.title brand_item.title
else else
'GitLab Enterprise Edition' 'GitLab Enterprise Edition'
......
...@@ -54,5 +54,17 @@ module AuthHelper ...@@ -54,5 +54,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"
...@@ -121,6 +120,18 @@ module IssuesHelper ...@@ -121,6 +120,18 @@ module IssuesHelper
end end
end end
def awards_sort(awards)
awards.sort_by do |award, notes|
if award == "thumbsup"
0
elsif award == "thumbsdown"
1
else
2
end
end.to_h
end
def projects_weight_options(selected = nil) def projects_weight_options(selected = nil)
options = (Issue::WEIGHT_RANGE).map do |i| options = (Issue::WEIGHT_RANGE).map do |i|
[i, i] [i, i]
......
...@@ -8,6 +8,80 @@ module PageLayoutHelper ...@@ -8,6 +8,80 @@ module PageLayoutHelper
@page_title.join(" \u00b7 ") @page_title.join(" \u00b7 ")
end end
# Define or get a description for the current page
#
# description - String (default: nil)
#
# If this helper is called multiple times with an argument, only the last
# description will be returned when called without an argument. Descriptions
# have newlines replaced with spaces and all HTML tags are sanitized.
#
# Examples:
#
# page_description # => "GitLab Community Edition"
# page_description("Foo")
# page_description # => "Foo"
#
# page_description("<b>Bar</b>\nBaz")
# page_description # => "Bar Baz"
#
# Returns an HTML-safe String.
def page_description(description = nil)
@page_description ||= page_description_default
if description.present?
@page_description = description.squish
else
sanitize(@page_description, tags: []).truncate_words(30)
end
end
# Default value for page_description when one hasn't been defined manually by
# a view
def page_description_default
if @project
@project.description || brand_title
else
brand_title
end
end
def page_image
default = image_url('gitlab_logo.png')
if @project
@project.avatar_url || default
elsif @user
avatar_icon(@user)
else
default
end
end
# Define or get attributes to be used as Twitter card metadata
#
# map - Hash of label => data pairs. Keys become labels, values become data
#
# Raises ArgumentError if given more than two attributes
def page_card_attributes(map = {})
raise ArgumentError, 'cannot provide more than two attributes' if map.length > 2
@page_card_attributes ||= {}
@page_card_attributes = map.reject { |_,v| v.blank? } if map.present?
@page_card_attributes
end
def page_card_meta_tags
tags = ''
page_card_attributes.each_with_index do |pair, i|
tags << tag(:meta, property: "twitter:label#{i + 1}", content: pair[0])
tags << tag(:meta, property: "twitter:data#{i + 1}", content: pair[1])
end
tags.html_safe
end
def header_title(title = nil, title_url = nil) def header_title(title = nil, title_url = nil)
if title if title
@header_title = title @header_title = title
......
...@@ -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
...@@ -148,14 +148,14 @@ class Ability ...@@ -148,14 +148,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,
...@@ -173,7 +173,7 @@ class Ability ...@@ -173,7 +173,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,
...@@ -186,7 +186,7 @@ class Ability ...@@ -186,7 +186,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,
...@@ -197,7 +197,7 @@ class Ability ...@@ -197,7 +197,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,
...@@ -207,7 +207,7 @@ class Ability ...@@ -207,7 +207,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,
...@@ -222,7 +222,7 @@ class Ability ...@@ -222,7 +222,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,
......
...@@ -29,6 +29,8 @@ ...@@ -29,6 +29,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
...@@ -59,6 +61,9 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -59,6 +61,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|
...@@ -113,6 +118,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -113,6 +118,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
...@@ -135,4 +142,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -135,4 +142,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
...@@ -161,6 +161,14 @@ module Issuable ...@@ -161,6 +161,14 @@ module Issuable
self.class.to_s.underscore self.class.to_s.underscore
end end
# Returns a Hash of attributes to be used for Twitter card metadata
def card_attributes
{
'Author' => author.try(:name),
'Assignee' => assignee.try(:name)
}
end
def notes_with_associations def notes_with_associations
notes.includes(:author, :project) notes.includes(:author, :project)
end end
......
...@@ -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
......
...@@ -107,9 +107,16 @@ class Note < ActiveRecord::Base ...@@ -107,9 +107,16 @@ class Note < ActiveRecord::Base
end end
def grouped_awards def grouped_awards
notes = {}
awards.select(:note).distinct.map do |note| awards.select(:note).distinct.map do |note|
[ note.note, where(note: note.note) ] notes[note.note] = where(note: note.note)
end end
notes["thumbsup"] ||= Note.none
notes["thumbsdown"] ||= Note.none
notes
end end
end end
......
...@@ -66,6 +66,19 @@ class Project < ActiveRecord::Base ...@@ -66,6 +66,19 @@ class Project < ActiveRecord::Base
after_destroy :remove_pages after_destroy :remove_pages
# 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
...@@ -106,10 +119,12 @@ class Project < ActiveRecord::Base ...@@ -106,10 +119,12 @@ class Project < ActiveRecord::Base
has_one :gitlab_issue_tracker_service, dependent: :destroy has_one :gitlab_issue_tracker_service, dependent: :destroy
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
...@@ -868,7 +883,7 @@ class Project < ActiveRecord::Base ...@@ -868,7 +883,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)
...@@ -985,6 +1000,11 @@ class Project < ActiveRecord::Base ...@@ -985,6 +1000,11 @@ class Project < ActiveRecord::Base
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
def pages_url def pages_url
if Dir.exist?(public_pages_path) if Dir.exist?(public_pages_path)
host = "#{namespace.path}.#{Settings.pages.host}" host = "#{namespace.path}.#{Settings.pages.host}"
......
...@@ -648,20 +648,30 @@ class Repository ...@@ -648,20 +648,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)
...@@ -685,10 +695,7 @@ class Repository ...@@ -685,10 +695,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
......
...@@ -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
......
...@@ -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")
......
...@@ -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
...@@ -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
......
...@@ -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
...@@ -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
......
- page_title "GitLab" %head{prefix: "og: http://ogp.me/ns#"}
%head
%meta{charset: "utf-8"} %meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
%meta{content: "GitLab Enterprise Edition", name: "description"}
%meta{name: 'referrer', content: 'origin-when-cross-origin'} %meta{name: 'referrer', content: 'origin-when-cross-origin'}
%meta{name: "description", content: page_description}
-# Open Graph - http://ogp.me/
%meta{property: 'og:type', content: "object"}
%meta{property: 'og:site_name', content: "GitLab"}
%meta{property: 'og:title', content: page_title}
%meta{property: 'og:description', content: page_description}
%meta{property: 'og:image', content: page_image}
%meta{property: 'og:url', content: request.base_url + request.fullpath}
-# Twitter Card - https://dev.twitter.com/cards/types/summary
%meta{property: 'twitter:card', content: "summary"}
%meta{property: 'twitter:title', content: page_title}
%meta{property: 'twitter:description', content: page_description}
%meta{property: 'twitter:image', content: page_image}
= page_card_meta_tags
- page_title "GitLab"
%title= page_title %title= page_title
= favicon_link_tag 'favicon.ico' = favicon_link_tag 'favicon.ico'
......
...@@ -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
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,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'
...@@ -46,3 +46,6 @@ ...@@ -46,3 +46,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'))
...@@ -20,6 +20,11 @@ ...@@ -20,6 +20,11 @@
= button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all' = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
= 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
disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file'); disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
new BlobFileDropzone($('.js-upload-blob-form'), '#{method}'); new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
:javascript :javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%div#tree-holder.tree-holder %div#tree-holder.tree-holder
= render 'blob', blob: @blob = render 'blob', blob: @blob
- if blob_editable?(@blob) - if can_edit_blob?(@blob)
= render 'projects/blob/remove' = render 'projects/blob/remove'
- title = "Replace #{@blob.name}" - title = "Replace #{@blob.name}"
......
...@@ -9,11 +9,12 @@ ...@@ -9,11 +9,12 @@
New Branch New Branch
%hr %hr
= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do = form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-create-branch-form js-requires-input" do
.form-group .form-group
= label_tag :branch_name, nil, class: 'control-label' = label_tag :branch_name, nil, class: 'control-label'
.col-sm-10 .col-sm-10
= text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control' = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name'
.help-block.text-danger.js-branch-name-error
.form-group .form-group
= label_tag :ref, 'Create from', class: 'control-label' = label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10 .col-sm-10
...@@ -26,7 +27,4 @@ ...@@ -26,7 +27,4 @@
:javascript :javascript
var availableRefs = #{@project.repository.ref_names.to_json}; var availableRefs = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({ new NewBranchForm($('.js-create-branch-form'), availableRefs)
source: availableRefs,
minLength: 1
});
...@@ -18,10 +18,11 @@ ...@@ -18,10 +18,11 @@
= link_to new_namespace_project_snippet_path(@project.namespace, @project) do = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw') = icon('file-text-o fw')
New snippet New snippet
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
%li.divider %li.divider
%li %li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), title: 'New file' do = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw') = icon('file fw')
New file New file
%li %li
...@@ -32,3 +33,20 @@ ...@@ -32,3 +33,20 @@
= link_to new_namespace_project_tag_path(@project.namespace, @project) do = link_to new_namespace_project_tag_path(@project.namespace, @project) do
= icon('tags fw') = icon('tags fw')
New tag New tag
- elsif current_user && current_user.already_forked?(@project)
%li.divider
%li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
New file
- elsif can?(current_user, :fork_project, @project)
%li.divider
%li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
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 fork_path, method: :post do
= icon('file fw')
New file
...@@ -4,10 +4,15 @@ ...@@ -4,10 +4,15 @@
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do
= icon('code-fork fw') = icon('code-fork fw')
Fork Fork
%div.count-with-arrow
%span.arrow
%span.count %span.count
= @project.forks_count = @project.forks_count
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do
= icon('code-fork fw') = icon('code-fork fw')
Fork
%div.count-with-arrow
%span.arrow
%span.count %span.count
= @project.forks_count = @project.forks_count
- if current_user - if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do
- if current_user.starred?(@project)
= icon('star fw') = icon('star fw')
%span.count %span.starred Unstar
- else
= icon('star-o fw')
%span Star
%div.count-with-arrow
%span.arrow
%span.count.star-count
= @project.star_count = @project.star_count
:javascript
$('.project-home-panel .toggle-star').on('ajax:success', function (e, data, status, xhr) {
$(this).replaceWith(data.html);
})
.on('ajax:error', function (e, xhr, status, error) {
new Flash('Star toggle failed. Try again later.', 'alert');
});
- else - else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw') = icon('star fw')
Star
%div.count-with-arrow
%span.arrow
%span.count %span.count
= @project.star_count = @project.star_count
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}" = "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}"
.diff-controls .diff-controls
- if blob_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments %i.fa.fa-comments
&nbsp; &nbsp;
...@@ -32,14 +32,15 @@ ...@@ -32,14 +32,15 @@
- if editable_diff?(diff_file) - if editable_diff?(diff_file)
= edit_blob_link(@merge_request.source_project, = edit_blob_link(@merge_request.source_project,
@merge_request.source_branch, diff_file.new_path, @merge_request.source_branch, diff_file.new_path,
after: '&nbsp;', from_merge_request_id: @merge_request.id) from_merge_request_id: @merge_request.id)
&nbsp;
= view_file_btn(diff_commit.id, diff_file, project) = view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines .diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs -# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?') - return unless blob.respond_to?('text?')
- if blob_viewable?(blob) - if blob_text_viewable?(blob)
- if diff_view == 'parallel' - if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else - else
......
...@@ -43,4 +43,3 @@ ...@@ -43,4 +43,3 @@
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
Forking repository Forking repository
%p Please wait a moment, this page will automatically refresh when ready. %p Please wait a moment, this page will automatically refresh when ready.
- content_for :note_actions do - content_for :note_actions do
- if can?(current_user, :update_issue, @issue) - if can?(current_user, :update_issue, @issue)
- if @issue.closed? - if @issue.closed?
= link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue' = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
- else - else
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue' = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close js-note-target-close', title: 'Close Issue'
#notes #notes
= render 'projects/notes/notes_with_form' = render 'projects/notes/notes_with_form'
- page_title "#{@issue.title} (##{@issue.iid})", "Issues" - page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
= render "header_title" = render "header_title"
.issue .issue
...@@ -23,16 +26,16 @@ ...@@ -23,16 +26,16 @@
.pull-right .pull-right
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do
= icon('plus') = icon('plus')
New Issue New Issue
- if can?(current_user, :update_issue, @issue) - if can?(current_user, :update_issue, @issue)
- if @issue.closed? - if @issue.closed?
= link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen' = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen'
- else - else
= link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue' = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close Issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
......
- content_for :note_actions do - content_for :note_actions do
- if can?(current_user, :update_merge_request, @merge_request) - if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open? - if @merge_request.open?
= link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
- if @merge_request.closed? - if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
#notes= render "projects/notes/notes_with_form" #notes= render "projects/notes/notes_with_form"
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
- if merge_request.open? && merge_request.broken? - if merge_request.open? && merge_request.broken?
%li %li
= link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: {container: 'body'} do = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
= icon('exclamation-triangle') = icon('exclamation-triangle')
- if merge_request.assignee - if merge_request.assignee
......
- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" - page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
= render "header_title" = render "header_title"
- if params[:view] == 'parallel' - if params[:view] == 'parallel'
......
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
.issue-btn-group.pull-right .issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request) - if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open? - if @merge_request.open?
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-grouped btn-close', title: 'Close merge request' = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-grouped issuable-edit', id: 'edit_merge_request' do = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
Edit Edit
- if @merge_request.closed? - if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
- page_title @milestone.title, "Milestones" - page_title @milestone.title, "Milestones"
- page_description @milestone.description
= render "header_title" = render "header_title"
.detail-page-header .detail-page-header
......
...@@ -13,6 +13,6 @@ ...@@ -13,6 +13,6 @@
.error-alert .error-alert
.note-form-actions.clearfix .note-form-actions.clearfix
= f.submit 'Add Comment', class: "btn btn-create comment-btn btn-grouped js-comment-button" = f.submit 'Add Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button"
= yield(:note_actions) = yield(:note_actions)
%a.btn.btn-cancel.js-close-discussion-note-form Cancel %a.btn.btn-cancel.js-close-discussion-note-form Cancel
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
- if tree.readme - if tree.readme
= render "projects/tree/readme", readme: tree.readme = render "projects/tree/readme", readme: tree.readme
- if allowed_tree_edit? - if can_edit_tree?
= render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post = render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
= render 'projects/blob/new_dir' = render 'projects/blob/new_dir'
......
...@@ -11,14 +11,20 @@ ...@@ -11,14 +11,20 @@
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path) = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else - else
= link_to title, '#' = link_to title, '#'
- if allowed_tree_edit?
- if current_user
%li %li
- if !on_top_of_branch?
%span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
= icon('plus')
- else
%span.dropdown %span.dropdown
%a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus') = icon('plus')
%ul.dropdown-menu %ul.dropdown-menu
- if can_edit_tree?
%li %li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
= icon('pencil fw') = icon('pencil fw')
New file New file
%li %li
...@@ -29,6 +35,35 @@ ...@@ -29,6 +35,35 @@
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
= icon('folder fw') = icon('folder fw')
New directory New directory
- elsif can?(current_user, :fork_project, @project)
%li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
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 fork_path, method: :post do
= icon('pencil fw')
New file
%li
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to upload a 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 fork_path, method: :post do
= icon('file fw')
Upload file
%li
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to create a new directory 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 fork_path, method: :post do
= icon('folder fw')
New directory
%li.divider %li.divider
%li %li
= link_to new_namespace_project_branch_path(@project.namespace, @project) do = link_to new_namespace_project_branch_path(@project.namespace, @project) do
...@@ -38,7 +73,3 @@ ...@@ -38,7 +73,3 @@
= link_to new_namespace_project_tag_path(@project.namespace, @project) do = link_to new_namespace_project_tag_path(@project.namespace, @project) do
= icon('tags fw') = icon('tags fw')
New tag New tag
- elsif !on_top_of_branch?
%li
%span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}}
= icon('plus')
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- if issue.description.present? - if issue.description.present?
.description.term .description.term
= preserve do = preserve do
= search_md_sanitize(markdown(issue.description)) = search_md_sanitize(markdown(issue.description, { project: issue.project }))
%span.light %span.light
#{issue.project.name_with_namespace} #{issue.project.name_with_namespace}
- if issue.closed? - if issue.closed?
......
- project = project || @project - project = project || @project
.git-clone-holder.input-group
.input-group-addon.git-protocols .git-clone-holder
.input-group-btn .btn-group.clone-options
= ssh_clone_button(project) %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'}
.input-group-btn %span
= http_clone_button(project) = default_clone_protocol.upcase
= icon('angle-down')
%ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
%li
%a#ssh-selector{href: @project.ssh_url_to_repo}
SSH
%li
%a#http-selector{href: @project.http_url_to_repo}
HTTPS
- if alternative_kerberos_url? - if alternative_kerberos_url?
.input-group-btn %li
= kerberos_clone_button(project) %a#kerberos-btn{href: @project.kerberos_url_to_repo}
KRB5
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn .input-group-btn
= clipboard_button(clipboard_target: '#project_clone') = clipboard_button(clipboard_target: '#project_clone')
:javascript
$('ul.clone-options-dropdown a').on('click',function(e){
e.preventDefault();
var $this = $(this);
$('a.clone-dropdown-btn span').text($this.text());
$('#project_clone').val($this.attr('href'));
});
= render 'shared/commit_message_container', placeholder: placeholder = render 'shared/commit_message_container', placeholder: placeholder
- unless @project.empty_repo? - if @project.empty_repo?
= hidden_field_tag 'target_branch', @ref
- else
- if can?(current_user, :push_code, @project)
.form-group.branch .form-group.branch
= label_tag 'new_branch', 'Target branch', class: 'control-label' = label_tag 'target_branch', 'Target branch', class: 'control-label'
.col-sm-10 .col-sm-10
= text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch" = text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch"
.js-create-merge-request-container .js-create-merge-request-container
.checkbox .checkbox
...@@ -12,5 +15,8 @@ ...@@ -12,5 +15,8 @@
= label_tag "create_merge_request-#{nonce}" do = label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
Start a <strong>new merge request</strong> with these changes Start a <strong>new merge request</strong> with these changes
- else
= hidden_field_tag 'target_branch', @target_branch || tree_edit_branch
= hidden_field_tag 'create_merge_request', 1
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch' = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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