Commit a30377c6 authored by Tomasz Maczukin's avatar Tomasz Maczukin

Merge branch 'master' into ci/api-builds

* master: (143 commits)
  Only load autocomplete data when actually needed
  Check for current user
  Add pencil icon to edit group settings
  Issue #5817 wording of the web hooks updated on issue and merge events
  use JavaScript instead of CoffeeScript in Views, the reason #9819
  Before project save ensure that a runners_token exists
  Fix Error 500 when visiting build page of project with nil runners_token
  Remove outdated gitlab-git-http-server reference from Install doc
  Fix typo in build page of projects
  Update docs for shared runner default settings
  Disable "Already Blocked" button in admin abuse report page
  Add CHANGELOG entry for reply-by-email fix
  Use WOFF versions of SourceSansPro
  Clean up document on adding users to a project
  Refactor ZenMode
  Fix caching issue where build status was not updating in project dashboard
  Add a CHANGELOG entry for The Most Important Feature of All Time(TM)
  changes verb `references` to noun `reference`.
  fixes new branch button positioning, when visible and not visible container
  DRY up upload and download services
  ...
parents 4e70f251 1ede18bf
...@@ -26,6 +26,7 @@ config/initializers/smtp_settings.rb ...@@ -26,6 +26,7 @@ config/initializers/smtp_settings.rb
config/resque.yml config/resque.yml
config/unicorn.rb config/unicorn.rb
config/secrets.yml config/secrets.yml
config/sidekiq.yml
coverage/* coverage/*
db/*.sqlite3 db/*.sqlite3
db/*.sqlite3-journal db/*.sqlite3-journal
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.4.0 (unreleased) v 8.4.0 (unreleased)
- Add housekeeping function to project settings page
- The default GitLab logo now acts as a loading indicator
- Fix caching issue where build status was not updating in project dashboard (Stan Hu)
- Accept 2xx status codes for successful Web hook triggers (Stan Hu)
- Fix missing date of month in network graph when commits span a month (Stan Hu)
- Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu) - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
- Don't notify users twice if they are both project watchers and subscribers (Stan Hu) - Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
- Implement new UI for group page - Implement new UI for group page
- Implement search inside emoji picker - Implement search inside emoji picker
- Add API support for looking up a user by username (Stan Hu) - Add API support for looking up a user by username (Stan Hu)
- Add project permissions to all project API endpoints (Stan Hu) - Add project permissions to all project API endpoints (Stan Hu)
- Link to milestone in "Milestone changed" system note
- Only allow group/project members to mention `@all` - Only allow group/project members to mention `@all`
- Expose Git's version in the admin area (Trey Davis) - Expose Git's version in the admin area (Trey Davis)
- Add "Frequently used" category to emoji picker - Add "Frequently used" category to emoji picker
- Add CAS support (tduehr) - Add CAS support (tduehr)
- Add link to merge request on build detail page - Add link to merge request on build detail page
- Fix: Problem with projects ending with .keys (Jose Corcuera)
- Revert back upvote and downvote button to the issue and MR pages - Revert back upvote and downvote button to the issue and MR pages
- Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg)
- Add system hook messages for project rename and transfer (Steve Norman) - Add system hook messages for project rename and transfer (Steve Norman)
- Fix version check image in Safari - Fix version check image in Safari
- Show 'All' tab by default in the builds page - Show 'All' tab by default in the builds page
- Add Open Graph and Twitter Card data to all pages
- Fix API project lookups when querying with a namespace with dots (Stan Hu) - Fix API project lookups when querying with a namespace with dots (Stan Hu)
- Update version check images to use SVG
- Validate README format before displaying
- Enable Microsoft Azure OAuth2 support (Janis Meybohm)
- Properly set task-list class on single item task lists
- Add file finder feature in tree view (Kyungchul Shin)
- Ajax filter by message for commits page
- API: Add support for deleting a tag via the API (Robert Schilling)
v 8.3.3 (unreleased) v 8.3.3 (unreleased)
- Preserve CE behavior with JIRA integration by only calling API if URL is set
- Fix duplicated branch creation/deletion events when using Web UI (Stan Hu)
- Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running
- Suppress e-mails on failed builds if allow_failure is set (Stan Hu)
- Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu)
- Enable "Add key" button when user fills in a proper key (Stan Hu) - Enable "Add key" button when user fills in a proper key (Stan Hu)
- Fix error in processing reply-by-email messages (Jason Lee)
- Fix Error 500 when visiting build page of project with nil runners_token (Stan Hu)
v 8.3.2 v 8.3.2
- Change single user API endpoint to return more detailed data (Michael Potthoff)
v 8.3.2 (unreleased)
- Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu)
- Add support for Google reCAPTCHA in user registration - Add support for Google reCAPTCHA in user registration
...@@ -33,6 +57,7 @@ v 8.3.1 ...@@ -33,6 +57,7 @@ v 8.3.1
- Fix LDAP identity and user retrieval when special characters are used - Fix LDAP identity and user retrieval when special characters are used
- Move Sidekiq-cron configuration to gitlab.yml - Move Sidekiq-cron configuration to gitlab.yml
- Enable forcing Two-Factor authentication sitewide, with optional grace period - Enable forcing Two-Factor authentication sitewide, with optional grace period
- Import GitHub Pull Requests into GitLab
v 8.3.0 v 8.3.0
- Bump rack-attack to 4.3.1 for security fix (Stan Hu) - Bump rack-attack to 4.3.1 for security fix (Stan Hu)
...@@ -96,6 +121,8 @@ v 8.3.0 ...@@ -96,6 +121,8 @@ 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
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
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)
...@@ -154,6 +181,8 @@ v 8.2.0 ...@@ -154,6 +181,8 @@ v 8.2.0
- Allow to define cache in `.gitlab-ci.yml` - Allow to define cache in `.gitlab-ci.yml`
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
- Remove deprecated CI events from project settings page - Remove deprecated CI events from project settings page
- Use issue editor as cross reference comment author when issue is edited with a new mention.
- Add graphs of commits ahead and behind default branch (Jeff Stubler)
- Improve personal snippet access workflow (Douglas Alexandre) - Improve personal snippet access workflow (Douglas Alexandre)
- [API] Add ability to fetch the commit ID of the last commit that actually touched a file - [API] Add ability to fetch the commit ID of the last commit that actually touched a file
- Fix omniauth documentation setting for omnibus configuration (Jon Cairns) - Fix omniauth documentation setting for omnibus configuration (Jon Cairns)
......
...@@ -33,6 +33,7 @@ gem 'omniauth-saml', '~> 1.4.0' ...@@ -33,6 +33,7 @@ gem 'omniauth-saml', '~> 1.4.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd' gem 'omniauth_crowd'
gem 'omniauth-azure-oauth2'
gem 'rack-oauth2', '~> 1.2.1' gem 'rack-oauth2', '~> 1.2.1'
# reCAPTCHA protection # reCAPTCHA protection
...@@ -48,7 +49,7 @@ gem "browser", '~> 1.0.0' ...@@ -48,7 +49,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.20' gem "gitlab_git", '~> 7.2.22'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -66,10 +67,6 @@ gem 'grape', '~> 0.13.0' ...@@ -66,10 +67,6 @@ gem 'grape', '~> 0.13.0'
gem 'grape-entity', '~> 0.4.2' gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Format dates and times
# based on human-friendly examples
gem "stamp", '~> 0.6.0'
# Pagination # Pagination
gem "kaminari", "~> 0.16.3" gem "kaminari", "~> 0.16.3"
......
...@@ -443,6 +443,10 @@ GEM ...@@ -443,6 +443,10 @@ GEM
omniauth (1.2.2) omniauth (1.2.2)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (~> 1.0) rack (~> 1.0)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-bitbucket (0.0.2) omniauth-bitbucket (0.0.2)
multi_json (~> 1.7) multi_json (~> 1.7)
omniauth (~> 1.1) omniauth (~> 1.1)
...@@ -730,7 +734,6 @@ GEM ...@@ -730,7 +734,6 @@ GEM
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
stamp (0.6.0)
state_machines (0.4.0) state_machines (0.4.0)
state_machines-activemodel (0.3.0) state_machines-activemodel (0.3.0)
activemodel (~> 4.1) activemodel (~> 4.1)
...@@ -883,7 +886,7 @@ DEPENDENCIES ...@@ -883,7 +886,7 @@ DEPENDENCIES
github-markup (~> 1.3.1) github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.2.0) gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.20) gitlab_git (~> 7.2.22)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
...@@ -916,6 +919,7 @@ DEPENDENCIES ...@@ -916,6 +919,7 @@ DEPENDENCIES
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.7.0) octokit (~> 3.7.0)
omniauth (~> 1.2.2) omniauth (~> 1.2.2)
omniauth-azure-oauth2
omniauth-bitbucket (~> 0.0.2) omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2) omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 3.0.0) omniauth-facebook (~> 3.0.0)
...@@ -973,7 +977,6 @@ DEPENDENCIES ...@@ -973,7 +977,6 @@ DEPENDENCIES
spring-commands-spinach (~> 1.0.0) spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 2.12.3) sprockets (~> 2.12.3)
stamp (~> 0.6.0)
state_machines-activerecord (~> 0.3.0) state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2) task_list (~> 1.0.2)
teaspoon (~> 1.0.0) teaspoon (~> 1.0.0)
...@@ -994,4 +997,4 @@ DEPENDENCIES ...@@ -994,4 +997,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.10.6 1.11.2
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1. This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
----------------------------------------------------------- -----------------------------------------------------------
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#= require shortcuts_network #= require shortcuts_network
#= require jquery.nicescroll.min #= require jquery.nicescroll.min
#= require_tree . #= require_tree .
#= require fuzzaldrin-plus.min
window.slugify = (text) -> window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......
...@@ -66,7 +66,7 @@ class @BranchGraph ...@@ -66,7 +66,7 @@ class @BranchGraph
r.rect(40, 0, 30, @barHeight).attr fill: "#444" r.rect(40, 0, 30, @barHeight).attr fill: "#444"
for day, mm in @days for day, mm in @days
if cuday isnt day[0] if cuday isnt day[0] || cumonth isnt day[1]
# Dates # Dates
r.text(55, @offsetY + @unitTime * mm, day[0]) r.text(55, @offsetY + @unitTime * mm, day[0])
.attr( .attr(
......
class @CommitsList class @CommitsList
@data = @timer = null
ref: null
limit: 0
offset: 0
@disable = false
@showProgress: ->
$('.loading').show()
@hideProgress: ->
$('.loading').hide()
@init: (ref, limit) -> @init: (ref, limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) -> $("body").on "click", ".day-commits-table li.commit", (event) ->
...@@ -18,38 +8,32 @@ class @CommitsList ...@@ -18,38 +8,32 @@ class @CommitsList
e.stopPropagation() e.stopPropagation()
return false return false
@data.ref = ref Pager.init limit, false
@data.limit = limit
@data.offset = limit @content = $("#commits-list")
@searchField = $("#commits-search")
@initSearch()
this.initLoadMore() @initSearch: ->
this.showProgress() @timer = null
@searchField.keyup =>
clearTimeout(@timer)
@timer = setTimeout(@filterResults, 500)
@filterResults: =>
form = $(".commits-search-form")
search = @searchField.val()
commitsUrl = form.attr("action") + '?' + form.serialize()
@content.fadeTo('fast', 0.5)
@getOld: ->
this.showProgress()
$.ajax $.ajax
type: "GET" type: "GET"
url: location.href url: form.attr("action")
data: @data data: form.serialize()
complete: this.hideProgress complete: =>
success: (data) -> @content.fadeTo('fast', 1.0)
CommitsList.append(data.count, data.html) success: (data) =>
@content.html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: commitsUrl}, document.title, commitsUrl
dataType: "json" dataType: "json"
@append: (count, html) ->
$("#commits-list").append(html)
if count > 0
@data.offset += count
else
@disable = true
@initLoadMore: ->
$(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: =>
@disable
callback: =>
this.getOld()
...@@ -87,7 +87,9 @@ class Dispatcher ...@@ -87,7 +87,9 @@ class Dispatcher
new GroupAvatar() new GroupAvatar()
when 'projects:tree:show' when 'projects:tree:show'
new TreeView() new TreeView()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsTree()
when 'projects:find_file:show'
shortcut_handler = true
when 'projects:blob:show' when 'projects:blob:show'
new LineHighlighter() new LineHighlighter()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
......
...@@ -66,7 +66,7 @@ class @DropzoneInput ...@@ -66,7 +66,7 @@ class @DropzoneInput
success: (header, response) -> success: (header, response) ->
child = $(dropzone[0]).children("textarea") child = $(dropzone[0]).children("textarea")
$(child).val $(child).val() + formatLink(response.link) + "\n" $(child).val $(child).val() + response.link.markdown + "\n"
return return
error: (temp, errorMessage) -> error: (temp, errorMessage) ->
...@@ -99,11 +99,6 @@ class @DropzoneInput ...@@ -99,11 +99,6 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea") child = $(dropzone[0]).children("textarea")
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
text
handlePaste = (event) -> handlePaste = (event) ->
pasteEvent = event.originalEvent pasteEvent = event.originalEvent
if pasteEvent.clipboardData and pasteEvent.clipboardData.items if pasteEvent.clipboardData and pasteEvent.clipboardData.items
...@@ -162,7 +157,7 @@ class @DropzoneInput ...@@ -162,7 +157,7 @@ class @DropzoneInput
closeAlertMessage() closeAlertMessage()
success: (e, textStatus, response) -> success: (e, textStatus, response) ->
insertToTextArea(filename, formatLink(response.responseJSON.link)) insertToTextArea(filename, response.responseJSON.link.markdown)
error: (response) -> error: (response) ->
showError(response.responseJSON.message) showError(response.responseJSON.message)
...@@ -202,8 +197,3 @@ class @DropzoneInput ...@@ -202,8 +197,3 @@ class @DropzoneInput
e.preventDefault() e.preventDefault()
$(@).closest('.gfm-form').find('.div-dropzone').click() $(@).closest('.gfm-form').find('.div-dropzone').click()
return return
formatLink: (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
text
...@@ -68,7 +68,7 @@ GitLab.GfmAutoComplete = ...@@ -68,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title) title: sanitize(m.title)
search: "#{m.iid} #{m.title}" search: "#{m.iid} #{m.title}"
input.one 'focus', => if @dataSource
$.getJSON(@dataSource).done (data) -> $.getJSON(@dataSource).done (data) ->
# load members # load members
input.atwho 'load', '@', data.members input.atwho 'load', '@', data.members
......
class @ProjectFindFile
constructor: (@element, @options)->
@filePaths = {}
@inputElement = @element.find(".file-finder-input")
# init event
@initEvent()
# focus text input box
@inputElement.focus()
# load file list
@load(@options.url)
# init event
initEvent: ->
@inputElement.off "keyup"
@inputElement.on "keyup", (event) =>
target = $(event.target)
value = target.val()
oldValue = target.data("oldValue") ? ""
if value != oldValue
target.data("oldValue", value)
@findFile()
@element.find("tr.tree-item").eq(0).addClass("selected").focus()
@element.find(".tree-content-holder .tree-table").on "click", (event) ->
if (event.target.nodeName != "A")
path = @element.find(".tree-item-file-name a", this).attr("href")
location.href = path if path
# find file
findFile: ->
searchText = @inputElement.val()
result = if searchText.length > 0 then fuzzaldrinPlus.filter(@filePaths, searchText) else @filePaths
@renderList result, searchText
# files pathes load
load: (url) ->
$.ajax
url: url
method: "get"
dataType: "json"
success: (data) =>
@element.find(".loading").hide()
@filePaths = data
@findFile()
@element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus()
# render result
renderList: (filePaths, searchText) ->
@element.find(".tree-table > tbody").empty()
for filePath, i in filePaths
break if i == 20
if searchText
matches = fuzzaldrinPlus.match(filePath, searchText)
blobItemUrl = "#{@options.blobUrlTemplate}/#{filePath}"
html = @makeHtml filePath, matches, blobItemUrl
@element.find(".tree-table > tbody").append(html)
# highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
highlighter = (element, text, matches) ->
lastIndex = 0
highlightText = ""
matchedChars = []
for matchIndex in matches
unmatched = text.substring(lastIndex, matchIndex)
if unmatched
element.append(matchedChars.join("").bold()) if matchedChars.length
matchedChars = []
element.append(document.createTextNode(unmatched))
matchedChars.push(text[matchIndex])
lastIndex = matchIndex + 1
element.append(matchedChars.join("").bold()) if matchedChars.length
element.append(document.createTextNode(text.substring(lastIndex)))
# make tbody row html
makeHtml: (filePath, matches, blobItemUrl) ->
$tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>")
if matches
$tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl))
else
$tr.find("a").attr("href", blobItemUrl).text(filePath)
return $tr
selectRow: (type) ->
rows = @element.find(".files-slider tr.tree-item")
selectedRow = @element.find(".files-slider tr.tree-item.selected")
if rows && rows.length > 0
if selectedRow && selectedRow.length > 0
if type == "UP"
next = selectedRow.prev()
else if type == "DOWN"
next = selectedRow.next()
if next.length > 0
selectedRow.removeClass "selected"
selectedRow = next
else
selectedRow = rows.eq(0)
selectedRow.addClass("selected").focus()
selectRowUp: =>
@selectRow "UP"
selectRowDown: =>
@selectRow "DOWN"
goToTree: =>
location.href = @options.treeUrl
goToBlob: =>
path = @element.find(".tree-item.selected .tree-item-file-name a").attr("href")
location.href = path if path
#= require shortcuts_navigation
class @ShortcutsFindFile extends ShortcutsNavigation
constructor: (@projectFindFile) ->
super()
_oldStopCallback = Mousetrap.stopCallback
# override to fire shortcuts action when focus in textbox
Mousetrap.stopCallback = (event, element, combo) =>
if element == @projectFindFile.inputElement[0] and (combo == 'up' or combo == 'down' or combo == 'esc' or combo == 'enter')
# when press up/down key in textbox, cusor prevent to move to home/end
event.preventDefault()
return false
return _oldStopCallback(event, element, combo)
Mousetrap.bind('up', @projectFindFile.selectRowUp)
Mousetrap.bind('down', @projectFindFile.selectRowDown)
Mousetrap.bind('esc', @projectFindFile.goToTree)
Mousetrap.bind('enter', @projectFindFile.goToBlob)
class @ShortcutsTree extends ShortcutsNavigation
constructor: ->
super()
Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file'))
# Zen Mode (full screen) textarea
#
#= provides zen_mode:enter
#= provides zen_mode:leave
#
#= require jquery.scrollTo
#= require dropzone #= require dropzone
#= require mousetrap #= require mousetrap
#= require mousetrap/pause #= require mousetrap/pause
#
# ### Events
#
# `zen_mode:enter`
#
# Fired when the "Edit in fullscreen" link is clicked.
#
# **Synchronicity** Sync
# **Bubbles** Yes
# **Cancelable** No
# **Target** a.js-zen-enter
#
# `zen_mode:leave`
#
# Fired when the "Leave Fullscreen" link is clicked.
#
# **Synchronicity** Sync
# **Bubbles** Yes
# **Cancelable** No
# **Target** a.js-zen-leave
#
class @ZenMode class @ZenMode
constructor: -> constructor: ->
@active_zen_area = null @active_backdrop = null
@active_checkbox = null @active_textarea = null
@scroll_position = 0
$(window).scroll => $(document).on 'click', '.js-zen-enter', (e) ->
if not @active_checkbox e.preventDefault()
@scroll_position = window.pageYOffset $(e.currentTarget).trigger('zen_mode:enter')
$('body').on 'click', '.zen-enter-link', (e) => $(document).on 'click', '.js-zen-leave', (e) ->
e.preventDefault() e.preventDefault()
$(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', true).change() $(e.currentTarget).trigger('zen_mode:leave')
$(document).on 'zen_mode:enter', (e) =>
@enter(e.target.parentNode)
$(document).on 'zen_mode:leave', (e) =>
@exit()
$('body').on 'click', '.zen-leave-link', (e) => $(document).on 'keydown', (e) ->
if e.keyCode == 27 # Esc
e.preventDefault() e.preventDefault()
$(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', false).change() $(document).trigger('zen_mode:leave')
$('body').on 'change', '.zen-toggle-comment', (e) => enter: (backdrop) ->
checkbox = e.currentTarget
if checkbox.checked
# Disable other keyboard shortcuts in ZEN mode
Mousetrap.pause() Mousetrap.pause()
@updateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) => @active_backdrop = $(backdrop)
if e.keyCode is 27 # Esc @active_backdrop.addClass('fullscreen')
@exitZenMode()
e.preventDefault() @active_textarea = @active_backdrop.find('textarea')
updateActiveZenArea: (checkbox) =>
@active_checkbox = $(checkbox)
@active_checkbox.prop('checked', true)
@active_zen_area = @active_checkbox.parent().find('textarea')
# Prevent a user-resized textarea from persisting to fullscreen # Prevent a user-resized textarea from persisting to fullscreen
@active_zen_area.removeAttr('style') @active_textarea.removeAttr('style')
@active_zen_area.focus() @active_textarea.focus()
exitZenMode: => exit: ->
if @active_zen_area isnt null if @active_textarea
Mousetrap.unpause() Mousetrap.unpause()
@active_checkbox.prop('checked', false)
@active_zen_area = null @active_textarea.closest('.zen-backdrop').removeClass('fullscreen')
@active_checkbox = null
@restoreScroll(@scroll_position) @scrollTo(@active_textarea)
# Enable dropzone when leaving ZEN mode
@active_textarea = null
@active_backdrop = null
Dropzone.forElement('.div-dropzone').enable() Dropzone.forElement('.div-dropzone').enable()
restoreScroll: (y) -> scrollTo: (zen_area) ->
window.scrollTo(window.pageXOffset, y) $.scrollTo(zen_area, 0, offset: -150)
...@@ -72,6 +72,15 @@ ...@@ -72,6 +72,15 @@
> p:last-child { > p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.block-controls {
float: right;
.control {
float: left;
margin-left: 10px;
}
}
} }
.cover-block { .cover-block {
......
...@@ -3,23 +3,23 @@ ...@@ -3,23 +3,23 @@
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf'); src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf.woff');
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf'); src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf.woff');
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf'); src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf.woff');
} }
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf'); src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf.woff');
} }
.zennable { .zennable {
.zen-toggle-comment { a.js-zen-enter {
display: none;
}
.zen-enter-link {
color: $gl-gray; color: $gl-gray;
position: absolute; position: absolute;
top: 0px; top: 0px;
...@@ -11,7 +7,7 @@ ...@@ -11,7 +7,7 @@
line-height: 40px; line-height: 40px;
} }
.zen-leave-link { a.js-zen-leave {
display: none; display: none;
color: $gl-text-color; color: $gl-text-color;
position: absolute; position: absolute;
...@@ -25,19 +21,8 @@ ...@@ -25,19 +21,8 @@
} }
} }
// Hide the Enter link when we're in Zen mode .zen-backdrop {
input:checked ~ .zen-backdrop .zen-enter-link { &.fullscreen {
display: none;
}
// Show the Leave link when we're in Zen mode
input:checked ~ .zen-backdrop .zen-leave-link {
display: block;
position: absolute;
top: 0;
}
input:checked ~ .zen-backdrop {
background-color: white; background-color: white;
position: fixed; position: fixed;
top: 0; top: 0;
...@@ -61,26 +46,16 @@ ...@@ -61,26 +46,16 @@
max-width: 900px; max-width: 900px;
margin: 0 auto; margin: 0 auto;
} }
}
// Make the color of the placeholder text in the Zenned-out textarea darker,
// so it becomes visible
input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder { a.js-zen-enter {
color: #A8A8A8; display: none;
} }
input:checked ~ .zen-backdrop textarea:-moz-placeholder { a.js-zen-leave {
color: #A8A8A8; display: block;
opacity: 1; position: absolute;
top: 0;
} }
input:checked ~ .zen-backdrop textarea::-moz-placeholder {
color: #A8A8A8;
opacity: 1;
} }
input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
color: #A8A8A8;
} }
} }
...@@ -28,10 +28,6 @@ ...@@ -28,10 +28,6 @@
} }
} }
.commits-feed-holder {
float: right;
}
li.commit { li.commit {
list-style: none; list-style: none;
...@@ -122,3 +118,59 @@ li.commit { ...@@ -122,3 +118,59 @@ li.commit {
color: $gl-gray; color: $gl-gray;
} }
} }
.divergence-graph {
padding: 12px 12px 0 0;
float: right;
.graph-side {
position: relative;
width: 80px;
height: 22px;
padding: 5px 0 13px;
float: left;
.bar {
position: absolute;
height: 4px;
background-color: #ccc;
}
.bar-behind {
right: 0;
border-radius: 3px 0 0 3px;
}
.bar-ahead {
left: 0;
border-radius: 0 3px 3px 0;
}
.count {
padding-top: 6px;
padding-bottom: 0px;
font-size: 12px;
color: #333;
display: block;
}
.count-behind {
padding-right: 4px;
text-align: right;
}
.count-ahead {
padding-left: 4px;
text-align: left;
}
}
.graph-separator {
position: relative;
width: 1px;
height: 18px;
margin: 5px 0 0;
float: left;
background-color: #ccc;
}
}
...@@ -138,6 +138,7 @@ ...@@ -138,6 +138,7 @@
*/ */
.event-last-push { .event-last-push {
overflow: auto; overflow: auto;
width: 100%;
.event-last-push-text { .event-last-push-text {
@include str-truncated(100%); @include str-truncated(100%);
padding: 5px 0; padding: 5px 0;
......
...@@ -94,9 +94,17 @@ ...@@ -94,9 +94,17 @@
} }
.cross-project-reference { .cross-project-reference {
font-weight: bold;
color: $gl-link-color; color: $gl-link-color;
span {
white-space: nowrap;
width: 85%;
overflow: hidden;
position: relative;
display: inline-block;
text-overflow: ellipsis;
}
button { button {
float: right; float: right;
} }
......
...@@ -411,10 +411,15 @@ ul.nav.nav-projects-tabs { ...@@ -411,10 +411,15 @@ ul.nav.nav-projects-tabs {
} }
} }
.last-push-widget {
margin-top: -1px;
}
.top-area { .top-area {
border-bottom: 1px solid #EEE; border-bottom: 1px solid #EEE;
margin: 0 -16px; margin: 0 -16px;
padding: 0 $gl-padding; padding: 0 $gl-padding;
height: 42px;
ul.left-top-menu { ul.left-top-menu {
display: inline-block; display: inline-block;
...@@ -521,6 +526,7 @@ pre.light-well { ...@@ -521,6 +526,7 @@ pre.light-well {
.projects-search-form { .projects-search-form {
margin: -$gl-padding; margin: -$gl-padding;
padding: $gl-padding; padding: $gl-padding;
padding-bottom: 0;
margin-bottom: 0px; margin-bottom: 0px;
input { input {
......
.tree-holder { .tree-holder {
.file-finder {
width: 50%;
.file-finder-input {
width: 95%;
display: inline-block;
}
}
.tree-table { .tree-table {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -70,8 +70,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -70,8 +70,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_enabled, :metrics_enabled,
:metrics_host, :metrics_host,
:metrics_port, :metrics_port,
:metrics_username,
:metrics_password,
:metrics_pool_size, :metrics_pool_size,
:metrics_timeout, :metrics_timeout,
:metrics_method_call_threshold, :metrics_method_call_threshold,
......
...@@ -9,6 +9,11 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -9,6 +9,11 @@ class Projects::BranchesController < Projects::ApplicationController
@sort = params[:sort] || 'name' @sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort) @branches = @repository.branches_sorted_by(@sort)
@branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE) @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE)
@max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch)
[memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
end
end end
def recent def recent
......
...@@ -8,10 +8,16 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -8,10 +8,16 @@ class Projects::CommitsController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
def show def show
@repo = @project.repository
@limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
search = params[:search]
@commits =
if search.present?
@repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact
else
@repository.commits(@ref, @path, @limit, @offset)
end
@commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = project.notes.where(commit_id: @commits.map(&:id)). @note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
......
# Controller for viewing a repository's file structure
class Projects::FindFileController < Projects::ApplicationController
include ExtractsPath
include ActionView::Helpers::SanitizeHelper
include TreeHelper
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
def show
return render_404 unless @repository.commit(@ref)
respond_to do |format|
format.html
end
end
def list
file_paths = @repo.ls_files(@ref)
respond_to do |format|
format.json { render json: file_paths }
end
end
end
...@@ -153,7 +153,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -153,7 +153,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def merge_check def merge_check
@merge_request.check_if_can_be_merged if @merge_request.unchecked? @merge_request.check_if_can_be_merged
render partial: "projects/merge_requests/widget/show.html.haml", layout: false render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end end
......
...@@ -20,6 +20,8 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -20,6 +20,8 @@ class Projects::RefsController < Projects::ApplicationController
namespace_project_network_path(@project.namespace, @project, @id, @options) namespace_project_network_path(@project.namespace, @project, @id, @options)
when "graphs" when "graphs"
namespace_project_graph_path(@project.namespace, @project, @id) namespace_project_graph_path(@project.namespace, @project, @id)
when "find_file"
namespace_project_find_file_path(@project.namespace, @project, @id)
when "graphs_commits" when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id) commits_namespace_project_graph_path(@project.namespace, @project, @id)
else else
......
...@@ -8,7 +8,7 @@ class ProjectsController < ApplicationController ...@@ -8,7 +8,7 @@ class ProjectsController < ApplicationController
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize # Authorize
before_action :authorize_admin_project!, only: [:edit, :update] before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping]
before_action :event_filter, only: [:show, :activity] before_action :event_filter, only: [:show, :activity]
layout :determine_layout layout :determine_layout
...@@ -166,6 +166,15 @@ class ProjectsController < ApplicationController ...@@ -166,6 +166,15 @@ class ProjectsController < ApplicationController
end end
end end
def housekeeping
::Projects::HousekeepingService.new(@project).execute
respond_to do |format|
flash[:notice] = "Housekeeping successfully started."
format.html { redirect_to project_path(@project) }
end
end
def toggle_star def toggle_star
current_user.toggle_star(@project) current_user.toggle_star(@project)
@project.reload @project.reload
......
...@@ -206,7 +206,7 @@ module ApplicationHelper ...@@ -206,7 +206,7 @@ module ApplicationHelper
element = content_tag :time, time.to_s, element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago js-timeago-pending", class: "#{html_class} js-timeago js-timeago-pending",
datetime: time.getutc.iso8601, datetime: time.getutc.iso8601,
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'), title: time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' } data: { toggle: 'tooltip', placement: placement, container: 'body' }
unless skip_js unless skip_js
......
module AuthHelper module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook).freeze PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled? def ldap_enabled?
......
...@@ -80,7 +80,7 @@ module IssuesHelper ...@@ -80,7 +80,7 @@ module IssuesHelper
xml.link href: namespace_project_issue_url(issue.project.namespace, xml.link href: namespace_project_issue_url(issue.project.namespace,
issue.project, issue) issue.project, issue)
xml.title truncate(issue.title, length: 80) xml.title truncate(issue.title, length: 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") xml.updated issue.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
xml.author do |author| xml.author do |author|
xml.name issue.author_name xml.name issue.author_name
......
...@@ -27,35 +27,20 @@ module PageLayoutHelper ...@@ -27,35 +27,20 @@ module PageLayoutHelper
# #
# Returns an HTML-safe String. # Returns an HTML-safe String.
def page_description(description = nil) def page_description(description = nil)
@page_description ||= page_description_default
if description.present? if description.present?
@page_description = description.squish @page_description = description.squish
else elsif @page_description.present?
sanitize(@page_description, tags: []).truncate_words(30) sanitize(@page_description, tags: []).truncate_words(30)
end end
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 def page_image
default = image_url('gitlab_logo.png') default = image_url('gitlab_logo.png')
if @project subject = @project || @user || @group
@project.avatar_url || default
elsif @user image = subject.avatar_url if subject.present?
avatar_icon(@user) image || default
else
default
end
end end
# Define or get attributes to be used as Twitter card metadata # Define or get attributes to be used as Twitter card metadata
......
...@@ -19,7 +19,7 @@ module SortingHelper ...@@ -19,7 +19,7 @@ module SortingHelper
end end
def sort_title_recently_updated def sort_title_recently_updated
'Recently updated' 'Last updated'
end end
def sort_title_oldest_created def sort_title_oldest_created
...@@ -27,7 +27,7 @@ module SortingHelper ...@@ -27,7 +27,7 @@ module SortingHelper
end end
def sort_title_recently_created def sort_title_recently_created
'Recently created' 'Last created'
end end
def sort_title_milestone_soon def sort_title_milestone_soon
......
...@@ -27,9 +27,20 @@ ...@@ -27,9 +27,20 @@
# admin_notification_email :string(255) # admin_notification_email :string(255)
# 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
# require_two_factor_authentication :boolean default(TRUE) # require_two_factor_authentication :boolean default(FALSE)
# two_factor_grace_period :integer default(48) # two_factor_grace_period :integer default(48)
# metrics_enabled :boolean default(FALSE)
# metrics_host :string default("localhost")
# metrics_username :string
# metrics_password :string
# metrics_pool_size :integer default(16)
# metrics_timeout :integer default(10)
# metrics_method_call_threshold :integer default(10)
# recaptcha_enabled :boolean default(FALSE)
# recaptcha_site_key :string
# recaptcha_private_key :string
# metrics_port :integer default(8089)
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
# target_url :string(255) # target_url :string(255)
# description :string(255) # description :string(255)
# artifacts_file :text # artifacts_file :text
# gl_project_id :integer
# #
module Ci module Ci
...@@ -54,6 +55,8 @@ module Ci ...@@ -54,6 +55,8 @@ module Ci
# To prevent db load megabytes of data from trace # To prevent db load megabytes of data from trace
default_scope -> { select(Ci::Build.columns_without_lazy) } default_scope -> { select(Ci::Build.columns_without_lazy) }
before_destroy { project }
class << self class << self
def columns_without_lazy def columns_without_lazy
(column_names - LAZY_ATTRIBUTES).map do |column_name| (column_names - LAZY_ATTRIBUTES).map do |column_name|
...@@ -149,10 +152,6 @@ module Ci ...@@ -149,10 +152,6 @@ module Ci
end end
end end
def project
commit.project
end
def project_id def project_id
commit.project.id commit.project.id
end end
...@@ -211,7 +210,7 @@ module Ci ...@@ -211,7 +210,7 @@ module Ci
def trace def trace
trace = raw_trace trace = raw_trace
if project && trace.present? if project && trace.present? && project.runners_token.present?
trace.gsub(project.runners_token, 'xxxxxx') trace.gsub(project.runners_token, 'xxxxxx')
else else
trace trace
......
...@@ -4,9 +4,10 @@ ...@@ -4,9 +4,10 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# runner_id :integer not null # runner_id :integer not null
# project_id :integer not null # project_id :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# gl_project_id :integer
# #
module Ci module Ci
......
...@@ -4,10 +4,11 @@ ...@@ -4,10 +4,11 @@
# #
# id :integer not null, primary key # id :integer not null, primary key
# token :string(255) # token :string(255)
# project_id :integer not null # project_id :integer
# deleted_at :datetime # deleted_at :datetime
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# gl_project_id :integer
# #
module Ci module Ci
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
# Table name: ci_variables # Table name: ci_variables
# #
# id :integer not null, primary key # id :integer not null, primary key
# project_id :integer not null # project_id :integer
# key :string(255) # key :string(255)
# value :text # value :text
# encrypted_value :text # encrypted_value :text
# encrypted_value_salt :string(255) # encrypted_value_salt :string(255)
# encrypted_value_iv :string(255) # encrypted_value_iv :string(255)
# gl_project_id :integer
# #
module Ci module Ci
......
# == Schema Information # == Schema Information
# #
# project_id integer # Table name: ci_builds
# status string #
# finished_at datetime # id :integer not null, primary key
# trace text # project_id :integer
# created_at datetime # status :string(255)
# updated_at datetime # finished_at :datetime
# started_at datetime # trace :text
# runner_id integer # created_at :datetime
# coverage float # updated_at :datetime
# commit_id integer # started_at :datetime
# commands text # runner_id :integer
# job_id integer # coverage :float
# name string # commit_id :integer
# deploy boolean default: false # commands :text
# options text # job_id :integer
# allow_failure boolean default: false, null: false # name :string(255)
# stage string # deploy :boolean default(FALSE)
# trigger_request_id integer # options :text
# stage_idx integer # allow_failure :boolean default(FALSE), not null
# tag boolean # stage :string(255)
# ref string # trigger_request_id :integer
# user_id integer # stage_idx :integer
# type string # tag :boolean
# target_url string # ref :string(255)
# description string # user_id :integer
# type :string(255)
# target_url :string(255)
# description :string(255)
# artifacts_file :text
# gl_project_id :integer
# #
class CommitStatus < ActiveRecord::Base class CommitStatus < ActiveRecord::Base
......
...@@ -51,8 +51,11 @@ module Mentionable ...@@ -51,8 +51,11 @@ module Mentionable
else else
self.class.mentionable_attrs.each do |attr, options| self.class.mentionable_attrs.each do |attr, options|
text = send(attr) text = send(attr)
options[:cache_key] = [self, attr] if options.delete(:cache) && self.persisted?
ext.analyze(text, options) context = options.dup
context[:cache_key] = [self, attr] if context.delete(:cache) && self.persisted?
ext.analyze(text, context)
end end
end end
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
# target_url :string(255) # target_url :string(255)
# description :string(255) # description :string(255)
# artifacts_file :text # artifacts_file :text
# gl_project_id :integer
# #
class GenericCommitStatus < CommitStatus class GenericCommitStatus < CommitStatus
......
...@@ -121,9 +121,9 @@ class GlobalMilestone ...@@ -121,9 +121,9 @@ class GlobalMilestone
def expires_at def expires_at
if due_date if due_date
if due_date.past? if due_date.past?
"expired at #{due_date.stamp("Aug 21, 2011")}" "expired on #{due_date.to_s(:medium)}"
else else
"expires at #{due_date.stamp("Aug 21, 2011")}" "expires on #{due_date.to_s(:medium)}"
end end
end end
end end
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
# type :string(255) # type :string(255)
# description :string(255) default(""), not null # description :string(255) default(""), not null
# avatar :string(255) # avatar :string(255)
# public :boolean default(FALSE)
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE) # tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null # note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE) # enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# #
class ProjectHook < WebHook class ProjectHook < WebHook
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE) # tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null # note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE) # enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# #
class ServiceHook < WebHook class ServiceHook < WebHook
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE) # tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null # note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE) # enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# #
class SystemHook < WebHook class SystemHook < WebHook
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE) # tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null # note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE) # enable_ssl_verification :boolean default(TRUE)
# build_events :boolean default(FALSE), not null
# #
class WebHook < ActiveRecord::Base class WebHook < ActiveRecord::Base
...@@ -60,7 +61,7 @@ class WebHook < ActiveRecord::Base ...@@ -60,7 +61,7 @@ class WebHook < ActiveRecord::Base
basic_auth: auth) basic_auth: auth)
end end
[response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)] [(response.code >= 200 && response.code < 300), ActionView::Base.full_sanitizer.sanitize(response.to_s)]
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}") logger.error("WebHook Error => #{e}")
[false, e.to_s] [false, e.to_s]
......
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
# locked_at :datetime # locked_at :datetime
# updated_by_id :integer # updated_by_id :integer
# merge_error :string(255) # merge_error :string(255)
# merge_params :text (serialized to hash) # merge_params :text
# merge_when_build_succeeds :boolean default(false), not null # merge_when_build_succeeds :boolean default(FALSE), not null
# merge_user_id :integer # merge_user_id :integer
# #
...@@ -229,6 +229,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -229,6 +229,8 @@ class MergeRequest < ActiveRecord::Base
end end
def check_if_can_be_merged def check_if_can_be_merged
return unless unchecked?
can_be_merged = can_be_merged =
project.repository.can_be_merged?(source_sha, target_branch) project.repository.can_be_merged?(source_sha, target_branch)
...@@ -252,7 +254,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -252,7 +254,11 @@ class MergeRequest < ActiveRecord::Base
end end
def mergeable? def mergeable?
open? && !work_in_progress? && can_be_merged? return false unless open? && !work_in_progress?
check_if_can_be_merged
can_be_merged?
end end
def gitlab_merge_status def gitlab_merge_status
...@@ -452,6 +458,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -452,6 +458,10 @@ class MergeRequest < ActiveRecord::Base
!source_branch_exists? || !target_branch_exists? !source_branch_exists? || !target_branch_exists?
end end
def broken?
self.commits.blank? || branch_missing? || cannot_be_merged?
end
def can_be_merged_by?(user) def can_be_merged_by?(user)
::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch) ::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
end end
...@@ -507,8 +517,4 @@ class MergeRequest < ActiveRecord::Base ...@@ -507,8 +517,4 @@ class MergeRequest < ActiveRecord::Base
def ci_commit def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end end
def broken?
self.commits.blank? || branch_missing? || cannot_be_merged?
end
end end
...@@ -22,6 +22,7 @@ class Milestone < ActiveRecord::Base ...@@ -22,6 +22,7 @@ class Milestone < ActiveRecord::Base
include InternalId include InternalId
include Sortable include Sortable
include Referable
include StripAttribute include StripAttribute
belongs_to :project belongs_to :project
...@@ -61,6 +62,27 @@ class Milestone < ActiveRecord::Base ...@@ -61,6 +62,27 @@ class Milestone < ActiveRecord::Base
end end
end end
def self.reference_pattern
nil
end
def self.link_reference_pattern
super("milestones", /(?<milestone>\d+)/)
end
def to_reference(from_project = nil)
escaped_title = self.title.gsub("]", "\\]")
h = Gitlab::Application.routes.url_helpers
url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
"[#{escaped_title}](#{url})"
end
def reference_link_text(from_project = nil)
self.title
end
def expired? def expired?
if due_date if due_date
due_date.past? due_date.past?
...@@ -90,9 +112,9 @@ class Milestone < ActiveRecord::Base ...@@ -90,9 +112,9 @@ class Milestone < ActiveRecord::Base
def expires_at def expires_at
if due_date if due_date
if due_date.past? if due_date.past?
"expired at #{due_date.stamp("Aug 21, 2011")}" "expired on #{due_date.to_s(:medium)}"
else else
"expires at #{due_date.stamp("Aug 21, 2011")}" "expires on #{due_date.to_s(:medium)}"
end end
end end
end end
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
# type :string(255) # type :string(255)
# description :string(255) default(""), not null # description :string(255) default(""), not null
# avatar :string(255) # avatar :string(255)
# public :boolean default(FALSE)
# #
class Namespace < ActiveRecord::Base class Namespace < ActiveRecord::Base
......
...@@ -29,6 +29,13 @@ ...@@ -29,6 +29,13 @@
# import_source :string(255) # import_source :string(255)
# commit_count :integer default(0) # commit_count :integer default(0)
# import_error :text # import_error :text
# ci_id :integer
# builds_enabled :boolean default(TRUE), not null
# shared_runners_enabled :boolean default(TRUE), not null
# runners_token :string
# build_coverage_regex :string
# build_allow_git_fetch :boolean default(TRUE), not null
# build_timeout :integer default(3600), not null
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -43,6 +50,7 @@ class Project < ActiveRecord::Base ...@@ -43,6 +50,7 @@ class Project < ActiveRecord::Base
include Sortable include Sortable
include AfterCommitQueue include AfterCommitQueue
include CaseSensitivity include CaseSensitivity
include TokenAuthenticatable
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
...@@ -186,10 +194,8 @@ class Project < ActiveRecord::Base ...@@ -186,10 +194,8 @@ class Project < ActiveRecord::Base
if: ->(project) { project.avatar.present? && project.avatar_changed? } if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :set_runners_token_token add_authentication_token_field :runners_token
def set_runners_token_token before_save :ensure_runners_token
self.runners_token = SecureRandom.hex(15) if self.runners_token.blank?
end
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
...@@ -775,6 +781,8 @@ class Project < ActiveRecord::Base ...@@ -775,6 +781,8 @@ class Project < ActiveRecord::Base
end end
def change_head(branch) def change_head(branch)
# Cached divergent commit counts are based on repository head
repository.expire_branch_cache
gitlab_shell.update_repository_head(self.path_with_namespace, branch) gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch reload_default_branch
end end
...@@ -891,4 +899,8 @@ class Project < ActiveRecord::Base ...@@ -891,4 +899,8 @@ class Project < ActiveRecord::Base
return true unless forked? return true unless forked?
Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i) Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
end end
def runners_token
ensure_runners_token!
end
end end
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
require 'asana' require 'asana'
class AsanaService < Service class AsanaService < Service
...@@ -40,8 +42,8 @@ get the commit comment added to it. ...@@ -40,8 +42,8 @@ get the commit comment added to it.
You can also close a task with a message containing: `fix #123456`. You can also close a task with a message containing: `fix #123456`.
You can find your Api Keys here: You can create a Personal Access Token here:
http://developer.asana.com/documentation/#api_keys' http://app.asana.com/-/account_api'
end end
def to_param def to_param
...@@ -53,14 +55,12 @@ http://developer.asana.com/documentation/#api_keys' ...@@ -53,14 +55,12 @@ http://developer.asana.com/documentation/#api_keys'
{ {
type: 'text', type: 'text',
name: 'api_key', name: 'api_key',
placeholder: 'User API token. User must have access to task, placeholder: 'User Personal Access Token. User must have access to task, all comments will be attributed to this user.'
all comments will be attributed to this user.'
}, },
{ {
type: 'text', type: 'text',
name: 'restrict_to_branch', name: 'restrict_to_branch',
placeholder: 'Comma-separated list of branches which will be placeholder: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
automatically inspected. Leave blank to include all branches.'
} }
] ]
end end
...@@ -69,58 +69,58 @@ automatically inspected. Leave blank to include all branches.' ...@@ -69,58 +69,58 @@ automatically inspected. Leave blank to include all branches.'
%w(push) %w(push)
end end
def client
@_client ||= begin
Asana::Client.new do |c|
c.authentication :access_token, api_key
end
end
end
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
Asana.configure do |client| # check the branch restriction is poplulated and branch is not included
client.api_key = api_key
end
user = data[:user_name]
branch = Gitlab::Git.ref_name(data[:ref]) branch = Gitlab::Git.ref_name(data[:ref])
branch_restriction = restrict_to_branch.to_s branch_restriction = restrict_to_branch.to_s
# check the branch restriction is poplulated and branch is not included
if branch_restriction.length > 0 && branch_restriction.index(branch).nil? if branch_restriction.length > 0 && branch_restriction.index(branch).nil?
return return
end end
user = data[:user_name]
project_name = project.name_with_namespace project_name = project.name_with_namespace
push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name
data[:commits].each do |commit| data[:commits].each do |commit|
check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg) push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):"
check_commit(commit[:message], push_msg)
end end
end end
def check_commit(message, push_msg) def check_commit(message, push_msg)
task_list = [] # matches either:
close_list = [] # - #1234
# - https://app.asana.com/0/0/1234
message.split("\n").each do |line| # optionally preceded with:
# look for a task ID or a full Asana url # - fix/ed/es/ing
task_list.concat(line.scan(/#(\d+)/)) # - close/s/d
task_list.concat(line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/)) # - closing
# look for a word starting with 'fix' followed by a task ID issue_finder = /(fix\w*|clos[ei]\w*+)?\W*(?:https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)|#(\d+))/i
close_list.concat(line.scan(/(fix\w*)\W*#(\d+)/i))
end message.scan(issue_finder).each do |tuple|
# tuple will be
# post commit to every taskid found # [ 'fix', 'id_from_url', 'id_from_pound' ]
task_list.each do |taskid| taskid = tuple[2] || tuple[1]
task = Asana::Task.find(taskid[0])
begin
if task task = Asana::Task.find_by_id(client, taskid)
task.create_story(text: push_msg + ' ' + message) task.add_comment(text: "#{push_msg} #{message}")
end
end if tuple[0]
task.update(completed: true)
# close all tasks that had 'fix(ed/es/ing) #:id' in them end
close_list.each do |taskid| rescue => e
task = Asana::Task.find(taskid.last) Rails.logger.error(e.message)
next
if task
task.modify(completed: true)
end end
end end
end end
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class AssemblaService < Service class AssemblaService < Service
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class BambooService < CiService class BambooService < CiService
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
require "addressable/uri" require "addressable/uri"
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class BuildsEmailService < Service class BuildsEmailService < Service
...@@ -72,12 +73,16 @@ class BuildsEmailService < Service ...@@ -72,12 +73,16 @@ class BuildsEmailService < Service
when 'success' when 'success'
!notify_only_broken_builds? !notify_only_broken_builds?
when 'failed' when 'failed'
true !allow_failure?(data)
else else
false false
end end
end end
def allow_failure?(data)
data[:build_allow_failure] == true
end
def all_recipients(data) def all_recipients(data)
all_recipients = recipients.split(',') all_recipients = recipients.split(',')
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class CampfireService < Service class CampfireService < Service
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
# Base class for CI services # Base class for CI services
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class CustomIssueTrackerService < IssueTrackerService class CustomIssueTrackerService < IssueTrackerService
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class DroneCiService < CiService class DroneCiService < CiService
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class EmailsOnPushService < Service class EmailsOnPushService < Service
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class ExternalWikiService < Service class ExternalWikiService < Service
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
require "flowdock-git-hook" require "flowdock-git-hook"
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
require "gemnasium/gitlab_service" require "gemnasium/gitlab_service"
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed # TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE) # merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE) # tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# build_events :boolean default(FALSE), not null
# #
class GitlabIssueTrackerService < IssueTrackerService class GitlabIssueTrackerService < IssueTrackerService
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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