Merge branch 'license-templates-and-api-12804' into 'master'
License templates when creating/editing a LICENSE file Closes #12804 See merge request !3660
This commit is contained in:
commit
3d4875f86a
|
@ -40,6 +40,8 @@ v 8.7.0 (unreleased)
|
|||
- Fix a bug whith trailing slash in teamcity_url (Charles May)
|
||||
- Allow back dating on issues when created or updated through the API
|
||||
- Allow back dating on issue notes when created through the API
|
||||
- Propose license template when creating a new LICENSE file
|
||||
- API: Expose /licenses and /licenses/:key
|
||||
- Fix avatar stretching by providing a cropping feature
|
||||
- API: Expose `subscribed` for issues and merge requests (Robert Schilling)
|
||||
- Allow SAML to handle external users based on user's information !3530
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -190,6 +190,9 @@ gem 'babosa', '~> 1.0.2'
|
|||
# Sanitizes SVG input
|
||||
gem "loofah", "~> 2.0.3"
|
||||
|
||||
# Working with license
|
||||
gem 'licensee', '~> 8.0.0'
|
||||
|
||||
# Protect against bruteforcing
|
||||
gem "rack-attack", '~> 4.3.1'
|
||||
|
||||
|
|
|
@ -452,6 +452,8 @@ GEM
|
|||
addressable (~> 2.3)
|
||||
letter_opener (1.1.2)
|
||||
launchy (~> 2.2)
|
||||
licensee (8.0.0)
|
||||
rugged (>= 0.24b)
|
||||
listen (3.0.5)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
|
@ -957,6 +959,7 @@ DEPENDENCIES
|
|||
jquery-ui-rails (~> 5.0.0)
|
||||
kaminari (~> 0.16.3)
|
||||
letter_opener (~> 1.1.2)
|
||||
licensee (~> 8.0.0)
|
||||
loofah (~> 2.0.3)
|
||||
mail_room (~> 0.6.1)
|
||||
method_source (~> 0.8)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
group_projects_path: "/api/:version/groups/:id/projects.json"
|
||||
projects_path: "/api/:version/projects.json"
|
||||
labels_path: "/api/:version/projects/:id/labels"
|
||||
license_path: "/api/:version/licenses/:key"
|
||||
|
||||
group: (group_id, callback) ->
|
||||
url = Api.buildUrl(Api.group_path)
|
||||
|
@ -92,6 +93,16 @@
|
|||
).done (projects) ->
|
||||
callback(projects)
|
||||
|
||||
# Return text for a specific license
|
||||
licenseText: (key, data, callback) ->
|
||||
url = Api.buildUrl(Api.license_path).replace(':key', key)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data: data
|
||||
).done (license) ->
|
||||
callback(license)
|
||||
|
||||
buildUrl: (url) ->
|
||||
url = gon.relative_url_root + url if gon.relative_url_root?
|
||||
return url.replace(':version', gon.api_version)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
class @BlobLicenseSelector
|
||||
licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
|
||||
|
||||
constructor: (editor) ->
|
||||
@$licenseSelector = $('.js-license-selector')
|
||||
$fileNameInput = $('#file_name')
|
||||
|
||||
initialFileNameValue = if $fileNameInput.length
|
||||
$fileNameInput.val()
|
||||
else if $('.editor-file-name').length
|
||||
$('.editor-file-name').text().trim()
|
||||
|
||||
@toggleLicenseSelector(initialFileNameValue)
|
||||
|
||||
if $fileNameInput
|
||||
$fileNameInput.on 'keyup blur', (e) =>
|
||||
@toggleLicenseSelector($(e.target).val())
|
||||
|
||||
$('select.license-select').on 'change', (e) ->
|
||||
data =
|
||||
project: $(this).data('project')
|
||||
fullname: $(this).data('fullname')
|
||||
Api.licenseText $(this).val(), data, (license) ->
|
||||
editor.setValue(license.content, -1)
|
||||
|
||||
toggleLicenseSelector: (fileName) =>
|
||||
if @licenseRegex.test(fileName)
|
||||
@$licenseSelector.show()
|
||||
else
|
||||
@$licenseSelector.hide()
|
|
@ -1,44 +1,39 @@
|
|||
class @EditBlob
|
||||
constructor: (assets_path, mode)->
|
||||
ace.config.set "modePath", assets_path + '/ace'
|
||||
constructor: (assets_path, ace_mode = null) ->
|
||||
ace.config.set "modePath", "#{assets_path}/ace"
|
||||
ace.config.loadModule "ace/ext/searchbox"
|
||||
if mode
|
||||
ace_mode = mode
|
||||
editor = ace.edit("editor")
|
||||
editor.focus()
|
||||
@editor = editor
|
||||
|
||||
if ace_mode
|
||||
editor.getSession().setMode "ace/mode/" + ace_mode
|
||||
@editor = ace.edit("editor")
|
||||
@editor.focus()
|
||||
@editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
|
||||
|
||||
# Before a form submission, move the content from the Ace editor into the
|
||||
# submitted textarea
|
||||
$('form').submit ->
|
||||
$("#file-content").val(editor.getValue())
|
||||
$('form').submit =>
|
||||
$("#file-content").val(@editor.getValue())
|
||||
|
||||
editModePanes = $(".js-edit-mode-pane")
|
||||
editModeLinks = $(".js-edit-mode a")
|
||||
editModeLinks.click (event) ->
|
||||
event.preventDefault()
|
||||
currentLink = $(this)
|
||||
paneId = currentLink.attr("href")
|
||||
currentPane = editModePanes.filter(paneId)
|
||||
editModeLinks.parent().removeClass "active hover"
|
||||
currentLink.parent().addClass "active hover"
|
||||
editModePanes.hide()
|
||||
if paneId is "#preview"
|
||||
currentPane.fadeIn 200
|
||||
$.post currentLink.data("preview-url"),
|
||||
content: editor.getValue()
|
||||
, (response) ->
|
||||
currentPane.empty().append response
|
||||
currentPane.syntaxHighlight()
|
||||
return
|
||||
@initModePanesAndLinks()
|
||||
new BlobLicenseSelector(@editor)
|
||||
|
||||
else
|
||||
currentPane.fadeIn 200
|
||||
editor.focus()
|
||||
return
|
||||
initModePanesAndLinks: ->
|
||||
@$editModePanes = $(".js-edit-mode-pane")
|
||||
@$editModeLinks = $(".js-edit-mode a")
|
||||
@$editModeLinks.click @editModeLinkClickHandler
|
||||
|
||||
editor: ->
|
||||
return @editor
|
||||
editModeLinkClickHandler: (event) =>
|
||||
event.preventDefault()
|
||||
currentLink = $(event.target)
|
||||
paneId = currentLink.attr("href")
|
||||
currentPane = @$editModePanes.filter(paneId)
|
||||
@$editModeLinks.parent().removeClass "active hover"
|
||||
currentLink.parent().addClass "active hover"
|
||||
@$editModePanes.hide()
|
||||
currentPane.fadeIn 200
|
||||
if paneId is "#preview"
|
||||
$.post currentLink.data("preview-url"),
|
||||
content: @editor.getValue()
|
||||
, (response) ->
|
||||
currentPane.empty().append response
|
||||
currentPane.syntaxHighlight()
|
||||
|
||||
else
|
||||
@editor.focus()
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
class @NewBlob
|
||||
constructor: (assets_path, mode)->
|
||||
ace.config.set "modePath", assets_path + '/ace'
|
||||
ace.config.loadModule "ace/ext/searchbox"
|
||||
if mode
|
||||
ace_mode = mode
|
||||
editor = ace.edit("editor")
|
||||
editor.focus()
|
||||
@editor = editor
|
||||
|
||||
if ace_mode
|
||||
editor.getSession().setMode "ace/mode/" + ace_mode
|
||||
|
||||
# Before a form submission, move the content from the Ace editor into the
|
||||
# submitted textarea
|
||||
$('form').submit ->
|
||||
$("#file-content").val(editor.getValue())
|
||||
|
||||
editor: ->
|
||||
return @editor
|
|
@ -26,6 +26,10 @@
|
|||
line-height: 42px;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
|
||||
.pull-right {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-ref {
|
||||
|
@ -53,4 +57,9 @@
|
|||
.select2 {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.encoding-selector,
|
||||
.license-selector {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,4 +173,15 @@ module BlobHelper
|
|||
response.etag = @blob.id
|
||||
!stale
|
||||
end
|
||||
|
||||
def licenses_for_select
|
||||
return @licenses_for_select if defined?(@licenses_for_select)
|
||||
|
||||
licenses = Licensee::License.all
|
||||
|
||||
@licenses_for_select = {
|
||||
Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
|
||||
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -216,40 +216,14 @@ module ProjectsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def add_contribution_guide_path(project)
|
||||
if project && !project.repository.contribution_guide
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch,
|
||||
file_name: "CONTRIBUTING.md",
|
||||
commit_message: "Add contribution guide"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_changelog_path(project)
|
||||
if project && !project.repository.changelog
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch,
|
||||
file_name: "CHANGELOG",
|
||||
commit_message: "Add changelog"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_license_path(project)
|
||||
if project && !project.repository.license
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch,
|
||||
file_name: "LICENSE",
|
||||
commit_message: "Add license"
|
||||
)
|
||||
end
|
||||
def add_special_file_path(project, file_name:, commit_message: nil)
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch || 'master',
|
||||
file_name: file_name,
|
||||
commit_message: commit_message || "Add #{file_name.downcase}"
|
||||
)
|
||||
end
|
||||
|
||||
def contribution_guide_path(project)
|
||||
|
@ -272,7 +246,7 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
def license_path(project)
|
||||
filename_path(project, :license)
|
||||
filename_path(project, :license_blob)
|
||||
end
|
||||
|
||||
def version_path(project)
|
||||
|
@ -306,6 +280,13 @@ module ProjectsHelper
|
|||
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
|
||||
end
|
||||
|
||||
def new_license_path
|
||||
ref = @repository.root_ref if @repository
|
||||
ref ||= 'master'
|
||||
|
||||
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
|
||||
end
|
||||
|
||||
def last_push_event
|
||||
if current_user
|
||||
current_user.recent_push(@project.id)
|
||||
|
@ -335,6 +316,12 @@ module ProjectsHelper
|
|||
@ref || @repository.try(:root_ref)
|
||||
end
|
||||
|
||||
def license_short_name(project)
|
||||
license = Licensee::License.new(project.repository.license_key)
|
||||
|
||||
license.nickname || license.name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filename_path(project, filename)
|
||||
|
|
|
@ -228,7 +228,8 @@ class Repository
|
|||
|
||||
def cache_keys
|
||||
%i(size branch_names tag_names commit_count
|
||||
readme version contribution_guide changelog license)
|
||||
readme version contribution_guide changelog
|
||||
license_blob license_key)
|
||||
end
|
||||
|
||||
def build_cache
|
||||
|
@ -461,27 +462,21 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
def license
|
||||
cache.fetch(:license) do
|
||||
licenses = tree(:head).blobs.find_all do |file|
|
||||
file.name =~ /\A(copying|license|licence)/i
|
||||
end
|
||||
def license_blob
|
||||
return nil if !exists? || empty?
|
||||
|
||||
preferences = [
|
||||
/\Alicen[sc]e\z/i, # LICENSE, LICENCE
|
||||
/\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
|
||||
/\Acopying\z/i, # COPYING
|
||||
/\Acopying\.(?!lesser)/i, # COPYING.txt
|
||||
/Acopying.lesser/i # COPYING.LESSER
|
||||
]
|
||||
|
||||
license = nil
|
||||
preferences.each do |r|
|
||||
license = licenses.find { |l| l.name =~ r }
|
||||
break if license
|
||||
cache.fetch(:license_blob) do
|
||||
if licensee_project.license
|
||||
blob_at_branch(root_ref, licensee_project.matched_file.filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
license
|
||||
def license_key
|
||||
return nil if !exists? || empty?
|
||||
|
||||
cache.fetch(:license_key) do
|
||||
licensee_project.license.try(:key) || 'no-license'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -964,4 +959,8 @@ class Repository
|
|||
def cache
|
||||
@cache ||= RepositoryCache.new(path_with_namespace)
|
||||
end
|
||||
|
||||
def licensee_project
|
||||
@licensee_project ||= Licensee.project(path)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
- else
|
||||
.gray-content-block.second-block.center
|
||||
%h3.page-title
|
||||
This project does not have README yet
|
||||
This project does not have a README yet
|
||||
- if can?(current_user, :push_code, @project)
|
||||
%p
|
||||
A
|
||||
|
@ -18,5 +18,5 @@
|
|||
distributed with computer software, forming part of its documentation.
|
||||
%p
|
||||
We recommend you to
|
||||
= link_to "add README", new_readme_path, class: 'underlined-link'
|
||||
= link_to "add a README", new_readme_path, class: 'underlined-link'
|
||||
file to the repository and GitLab will render it here instead of this message.
|
||||
|
|
|
@ -13,7 +13,11 @@
|
|||
required: true, class: 'form-control new-file-name'
|
||||
|
||||
.pull-right
|
||||
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
|
||||
.license-selector.js-license-selector.hide
|
||||
= select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name}
|
||||
|
||||
.encoding-selector
|
||||
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
|
||||
|
||||
.file-content.code
|
||||
%pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
|
||||
|
|
|
@ -14,5 +14,5 @@
|
|||
cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
|
||||
|
||||
:javascript
|
||||
blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
|
||||
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}")
|
||||
new NewCommitForm($('.js-new-blob-form'))
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
%p
|
||||
If you already have files you can push them using command line instructions below.
|
||||
%p
|
||||
Otherwise you can start with
|
||||
= link_to "adding README", new_readme_path, class: 'underlined-link'
|
||||
Otherwise you can start with adding a
|
||||
= link_to "README", new_readme_path, class: 'underlined-link'
|
||||
or a
|
||||
= link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link'
|
||||
file to this project.
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
%li
|
||||
= link_to 'Changelog', changelog_path(@project)
|
||||
|
||||
- if @repository.license
|
||||
- if @repository.license_blob
|
||||
%li
|
||||
= link_to 'License', license_path(@project)
|
||||
= link_to license_short_name(@project), license_path(@project)
|
||||
|
||||
- if @repository.contribution_guide
|
||||
%li
|
||||
|
@ -47,15 +47,15 @@
|
|||
- if current_user && can_push_branch?(@project, @project.default_branch)
|
||||
- unless @repository.changelog
|
||||
%li.missing
|
||||
= link_to add_changelog_path(@project) do
|
||||
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
|
||||
Add Changelog
|
||||
- unless @repository.license
|
||||
- unless @repository.license_blob
|
||||
%li.missing
|
||||
= link_to add_license_path(@project) do
|
||||
= link_to add_special_file_path(@project, file_name: 'LICENSE') do
|
||||
Add License
|
||||
- unless @repository.contribution_guide
|
||||
%li.missing
|
||||
= link_to add_contribution_guide_path(@project) do
|
||||
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
|
||||
Add Contribution guide
|
||||
|
||||
- if @repository.commit
|
||||
|
|
|
@ -33,6 +33,7 @@ following locations:
|
|||
- [Build triggers](build_triggers.md)
|
||||
- [Build Variables](build_variables.md)
|
||||
- [Runners](runners.md)
|
||||
- [Licenses](licenses.md)
|
||||
|
||||
## Authentication
|
||||
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
# Licenses
|
||||
|
||||
## List license templates
|
||||
|
||||
Get all license templates.
|
||||
|
||||
```
|
||||
GET /licenses
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ------- | -------- | --------------------- |
|
||||
| `popular` | boolean | no | If passed, returns only popular licenses |
|
||||
|
||||
```bash
|
||||
curl https://gitlab.example.com/api/v3/licenses?popular=1
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"key": "apache-2.0",
|
||||
"name": "Apache License 2.0",
|
||||
"nickname": null,
|
||||
"featured": true,
|
||||
"html_url": "http://choosealicense.com/licenses/apache-2.0/",
|
||||
"source_url": "http://www.apache.org/licenses/LICENSE-2.0.html",
|
||||
"description": "A permissive license that also provides an express grant of patent rights from contributors to users.",
|
||||
"conditions": [
|
||||
"include-copyright",
|
||||
"document-changes"
|
||||
],
|
||||
"permissions": [
|
||||
"commercial-use",
|
||||
"modifications",
|
||||
"distribution",
|
||||
"patent-use",
|
||||
"private-use"
|
||||
],
|
||||
"limitations": [
|
||||
"trademark-use",
|
||||
"no-liability"
|
||||
],
|
||||
"content": " Apache License\n Version 2.0, January 2004\n [...]"
|
||||
},
|
||||
{
|
||||
"key": "gpl-3.0",
|
||||
"name": "GNU General Public License v3.0",
|
||||
"nickname": "GNU GPLv3",
|
||||
"featured": true,
|
||||
"html_url": "http://choosealicense.com/licenses/gpl-3.0/",
|
||||
"source_url": "http://www.gnu.org/licenses/gpl-3.0.txt",
|
||||
"description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.",
|
||||
"conditions": [
|
||||
"include-copyright",
|
||||
"document-changes",
|
||||
"disclose-source",
|
||||
"same-license"
|
||||
],
|
||||
"permissions": [
|
||||
"commercial-use",
|
||||
"modifications",
|
||||
"distribution",
|
||||
"patent-use",
|
||||
"private-use"
|
||||
],
|
||||
"limitations": [
|
||||
"no-liability"
|
||||
],
|
||||
"content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]"
|
||||
},
|
||||
{
|
||||
"key": "mit",
|
||||
"name": "MIT License",
|
||||
"nickname": null,
|
||||
"featured": true,
|
||||
"html_url": "http://choosealicense.com/licenses/mit/",
|
||||
"source_url": "http://opensource.org/licenses/MIT",
|
||||
"description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.",
|
||||
"conditions": [
|
||||
"include-copyright"
|
||||
],
|
||||
"permissions": [
|
||||
"commercial-use",
|
||||
"modifications",
|
||||
"distribution",
|
||||
"private-use"
|
||||
],
|
||||
"limitations": [
|
||||
"no-liability"
|
||||
],
|
||||
"content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Single license template
|
||||
|
||||
Get a single license template. You can pass parameters to replace the license
|
||||
placeholder.
|
||||
|
||||
```
|
||||
GET /licenses/:key
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ---------- | ------ | -------- | ----------- |
|
||||
| `key` | string | yes | The key of the license template |
|
||||
| `project` | string | no | The copyrighted project name |
|
||||
| `fullname` | string | no | The full-name of the copyright holder |
|
||||
|
||||
>**Note:**
|
||||
If you omit the `fullname` parameter but authenticate your request, the name of
|
||||
the authenticated user will be used to replace the copyright holder placeholder.
|
||||
|
||||
```bash
|
||||
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "mit",
|
||||
"name": "MIT License",
|
||||
"nickname": null,
|
||||
"featured": true,
|
||||
"html_url": "http://choosealicense.com/licenses/mit/",
|
||||
"source_url": "http://opensource.org/licenses/MIT",
|
||||
"description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.",
|
||||
"conditions": [
|
||||
"include-copyright"
|
||||
],
|
||||
"permissions": [
|
||||
"commercial-use",
|
||||
"modifications",
|
||||
"distribution",
|
||||
"private-use"
|
||||
],
|
||||
"limitations": [
|
||||
"no-liability"
|
||||
],
|
||||
"content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]"
|
||||
}
|
||||
```
|
|
@ -123,19 +123,6 @@ Feature: Project Source Browse Files
|
|||
And I am redirected to the fork's new merge request page
|
||||
And I can see the replacement commit message
|
||||
|
||||
@javascript
|
||||
Scenario: I can create file in empty repo
|
||||
Given I own an empty project
|
||||
And I visit my empty project page
|
||||
And I create bare repo
|
||||
When I click on "add a file" link
|
||||
And I edit code
|
||||
And I fill the new file name
|
||||
And I fill the commit message
|
||||
And I click on "Commit Changes"
|
||||
Then I am redirected to the new file
|
||||
And I should see its new content
|
||||
|
||||
@javascript
|
||||
Scenario: If I enter an illegal file name I see an error message
|
||||
Given I click on "New file" link in repo
|
||||
|
|
|
@ -282,8 +282,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
|||
click_link 'Create empty bare repository'
|
||||
end
|
||||
|
||||
step 'I click on "add a file" link' do
|
||||
click_link 'adding README'
|
||||
step 'I click on "README" link' do
|
||||
click_link 'README'
|
||||
|
||||
# Remove pre-receive hook so we can push without auth
|
||||
FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
|
||||
|
|
|
@ -57,5 +57,6 @@ module API
|
|||
mount Builds
|
||||
mount Variables
|
||||
mount Runners
|
||||
mount Licenses
|
||||
end
|
||||
end
|
||||
|
|
|
@ -439,5 +439,17 @@ module API
|
|||
class Variable < Grape::Entity
|
||||
expose :key, :value
|
||||
end
|
||||
|
||||
class RepoLicense < Grape::Entity
|
||||
expose :key, :name, :nickname
|
||||
expose :featured, as: :popular
|
||||
expose :url, as: :html_url
|
||||
expose(:source_url) { |license| license.meta['source'] }
|
||||
expose(:description) { |license| license.meta['description'] }
|
||||
expose(:conditions) { |license| license.meta['conditions'] }
|
||||
expose(:permissions) { |license| license.meta['permissions'] }
|
||||
expose(:limitations) { |license| license.meta['limitations'] }
|
||||
expose :content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
module API
|
||||
# Licenses API
|
||||
class Licenses < Grape::API
|
||||
PROJECT_TEMPLATE_REGEX =
|
||||
/[\<\{\[]
|
||||
(project|description|
|
||||
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
|
||||
[\>\}\]]/xi.freeze
|
||||
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
|
||||
FULLNAME_TEMPLATE_REGEX =
|
||||
/[\<\{\[]
|
||||
(fullname|name\sof\s(author|copyright\sowner))
|
||||
[\>\}\]]/xi.freeze
|
||||
|
||||
# Get the list of the available license templates
|
||||
#
|
||||
# Parameters:
|
||||
# popular - Filter licenses to only the popular ones
|
||||
#
|
||||
# Example Request:
|
||||
# GET /licenses
|
||||
# GET /licenses?popular=1
|
||||
get 'licenses' do
|
||||
options = {
|
||||
featured: params[:popular].present? ? true : nil
|
||||
}
|
||||
present Licensee::License.all(options), with: Entities::RepoLicense
|
||||
end
|
||||
|
||||
# Get text for specific license
|
||||
#
|
||||
# Parameters:
|
||||
# key (required) - The key of a license
|
||||
# project - Copyrighted project name
|
||||
# fullname - Full name of copyright holder
|
||||
#
|
||||
# Example Request:
|
||||
# GET /licenses/mit
|
||||
#
|
||||
get 'licenses/:key', requirements: { key: /[\w\.-]+/ } do
|
||||
required_attributes! [:key]
|
||||
|
||||
not_found!('License') unless Licensee::License.find(params[:key])
|
||||
|
||||
# We create a fresh Licensee::License object since we'll modify its
|
||||
# content in place below.
|
||||
license = Licensee::License.new(params[:key])
|
||||
|
||||
license.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
|
||||
license.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
|
||||
|
||||
fullname = params[:fullname].presence || current_user.try(:name)
|
||||
license.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
|
||||
|
||||
present license, with: Entities::RepoLicense
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'project owner creates a license file', feature: true, js: true do
|
||||
include Select2Helper
|
||||
|
||||
let(:project_master) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
background do
|
||||
project.repository.remove_file(project_master, 'LICENSE', 'Remove LICENSE', 'master')
|
||||
project.team << [project_master, :master]
|
||||
login_as(project_master)
|
||||
visit namespace_project_path(project.namespace, project)
|
||||
end
|
||||
|
||||
scenario 'project master creates a license file manually from a template' do
|
||||
visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
|
||||
find('.add-to-tree').click
|
||||
click_link 'New file'
|
||||
|
||||
fill_in :file_name, with: 'LICENSE'
|
||||
|
||||
expect(page).to have_selector('.license-selector')
|
||||
|
||||
select2('mit', from: '#license_type')
|
||||
|
||||
file_content = find('.file-content')
|
||||
expect(file_content).to have_content('The MIT License (MIT)')
|
||||
expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
|
||||
|
||||
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
|
||||
click_button 'Commit Changes'
|
||||
|
||||
expect(current_path).to eq(
|
||||
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
|
||||
expect(page).to have_content('The MIT License (MIT)')
|
||||
expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
|
||||
end
|
||||
|
||||
scenario 'project master creates a license file from the "Add license" link' do
|
||||
click_link 'Add License'
|
||||
|
||||
expect(current_path).to eq(
|
||||
namespace_project_new_blob_path(project.namespace, project, 'master'))
|
||||
expect(find('#file_name').value).to eq('LICENSE')
|
||||
expect(page).to have_selector('.license-selector')
|
||||
|
||||
select2('mit', from: '#license_type')
|
||||
|
||||
file_content = find('.file-content')
|
||||
expect(file_content).to have_content('The MIT License (MIT)')
|
||||
expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
|
||||
|
||||
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
|
||||
click_button 'Commit Changes'
|
||||
|
||||
expect(current_path).to eq(
|
||||
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
|
||||
expect(page).to have_content('The MIT License (MIT)')
|
||||
expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'project owner sees a link to create a license file in empty project', feature: true, js: true do
|
||||
include Select2Helper
|
||||
|
||||
let(:project_master) { create(:user) }
|
||||
let(:project) { create(:empty_project) }
|
||||
background do
|
||||
project.team << [project_master, :master]
|
||||
login_as(project_master)
|
||||
end
|
||||
|
||||
scenario 'project master creates a license file from a template' do
|
||||
visit namespace_project_path(project.namespace, project)
|
||||
click_link 'Create empty bare repository'
|
||||
click_on 'LICENSE'
|
||||
|
||||
expect(current_path).to eq(
|
||||
namespace_project_new_blob_path(project.namespace, project, 'master'))
|
||||
expect(find('#file_name').value).to eq('LICENSE')
|
||||
expect(page).to have_selector('.license-selector')
|
||||
|
||||
select2('mit', from: '#license_type')
|
||||
|
||||
file_content = find('.file-content')
|
||||
expect(file_content).to have_content('The MIT License (MIT)')
|
||||
expect(file_content).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
|
||||
|
||||
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
|
||||
# Remove pre-receive hook so we can push without auth
|
||||
FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
|
||||
click_button 'Commit Changes'
|
||||
|
||||
expect(current_path).to eq(
|
||||
namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
|
||||
expect(page).to have_content('The MIT License (MIT)')
|
||||
expect(page).to have_content("Copyright (c) 2016 #{project.namespace.human_name}")
|
||||
end
|
||||
end
|
|
@ -135,22 +135,69 @@ describe Repository, models: true do
|
|||
|
||||
end
|
||||
|
||||
describe "#license" do
|
||||
describe '#license_blob' do
|
||||
before do
|
||||
repository.send(:cache).expire(:license)
|
||||
repository.send(:cache).expire(:license_blob)
|
||||
repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
|
||||
end
|
||||
|
||||
it 'test selection preference' do
|
||||
files = [TestBlob.new('file'), TestBlob.new('license'), TestBlob.new('copying')]
|
||||
expect(repository.tree).to receive(:blobs).and_return(files)
|
||||
it 'looks in the root_ref only' do
|
||||
repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'markdown')
|
||||
repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'markdown', false)
|
||||
|
||||
expect(repository.license.name).to eq('license')
|
||||
expect(repository.license_blob).to be_nil
|
||||
end
|
||||
|
||||
it 'also accepts licence instead of license' do
|
||||
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('licence')])
|
||||
it 'favors license file with no extension' do
|
||||
repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
|
||||
repository.commit_file(user, 'LICENSE.md', Licensee::License.new('mit').content, 'Add LICENSE.md', 'master', false)
|
||||
|
||||
expect(repository.license.name).to eq('licence')
|
||||
expect(repository.license_blob.name).to eq('LICENSE')
|
||||
end
|
||||
|
||||
it 'favors .md file to .txt' do
|
||||
repository.commit_file(user, 'LICENSE.md', Licensee::License.new('mit').content, 'Add LICENSE.md', 'master', false)
|
||||
repository.commit_file(user, 'LICENSE.txt', Licensee::License.new('mit').content, 'Add LICENSE.txt', 'master', false)
|
||||
|
||||
expect(repository.license_blob.name).to eq('LICENSE.md')
|
||||
end
|
||||
|
||||
it 'favors LICENCE to LICENSE' do
|
||||
repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
|
||||
repository.commit_file(user, 'LICENCE', Licensee::License.new('mit').content, 'Add LICENCE', 'master', false)
|
||||
|
||||
expect(repository.license_blob.name).to eq('LICENCE')
|
||||
end
|
||||
|
||||
it 'favors LICENSE to COPYING' do
|
||||
repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
|
||||
repository.commit_file(user, 'COPYING', Licensee::License.new('mit').content, 'Add COPYING', 'master', false)
|
||||
|
||||
expect(repository.license_blob.name).to eq('LICENSE')
|
||||
end
|
||||
|
||||
it 'favors LICENCE to COPYING' do
|
||||
repository.commit_file(user, 'LICENCE', Licensee::License.new('mit').content, 'Add LICENCE', 'master', false)
|
||||
repository.commit_file(user, 'COPYING', Licensee::License.new('mit').content, 'Add COPYING', 'master', false)
|
||||
|
||||
expect(repository.license_blob.name).to eq('LICENCE')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#license_key' do
|
||||
before do
|
||||
repository.send(:cache).expire(:license_key)
|
||||
repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
|
||||
end
|
||||
|
||||
it 'returns "no-license" when no license is detected' do
|
||||
expect(repository.license_key).to eq('no-license')
|
||||
end
|
||||
|
||||
it 'returns the license key' do
|
||||
repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
|
||||
|
||||
expect(repository.license_key).to eq('mit')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe API::Licenses, api: true do
|
||||
include ApiHelpers
|
||||
|
||||
describe 'Entity' do
|
||||
before { get api('/licenses/mit') }
|
||||
|
||||
it { expect(json_response['key']).to eq('mit') }
|
||||
it { expect(json_response['name']).to eq('MIT License') }
|
||||
it { expect(json_response['nickname']).to be_nil }
|
||||
it { expect(json_response['popular']).to be true }
|
||||
it { expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') }
|
||||
it { expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') }
|
||||
it { expect(json_response['description']).to include('A permissive license that is short and to the point.') }
|
||||
it { expect(json_response['conditions']).to eq(%w[include-copyright]) }
|
||||
it { expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) }
|
||||
it { expect(json_response['limitations']).to eq(%w[no-liability]) }
|
||||
it { expect(json_response['content']).to include('The MIT License (MIT)') }
|
||||
end
|
||||
|
||||
describe 'GET /licenses' do
|
||||
it 'returns a list of available license templates' do
|
||||
get api('/licenses')
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.size).to eq(15)
|
||||
expect(json_response.map { |l| l['key'] }).to include('agpl-3.0')
|
||||
end
|
||||
|
||||
describe 'the popular parameter' do
|
||||
context 'with popular=1' do
|
||||
it 'returns a list of available popular license templates' do
|
||||
get api('/licenses?popular=1')
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.size).to eq(3)
|
||||
expect(json_response.map { |l| l['key'] }).to include('apache-2.0')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /licenses/:key' do
|
||||
context 'with :project and :fullname given' do
|
||||
before do
|
||||
get api("/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}")
|
||||
end
|
||||
|
||||
context 'for the mit license' do
|
||||
let(:license_type) { 'mit' }
|
||||
|
||||
it 'returns the license text' do
|
||||
expect(json_response['content']).to include('The MIT License (MIT)')
|
||||
end
|
||||
|
||||
it 'replaces placeholder values' do
|
||||
expect(json_response['content']).to include('Copyright (c) 2016 Anton')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for the agpl-3.0 license' do
|
||||
let(:license_type) { 'agpl-3.0' }
|
||||
|
||||
it 'returns the license text' do
|
||||
expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE')
|
||||
end
|
||||
|
||||
it 'replaces placeholder values' do
|
||||
expect(json_response['content']).to include('My Awesome Project')
|
||||
expect(json_response['content']).to include('Copyright (C) 2016 Anton')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for the gpl-3.0 license' do
|
||||
let(:license_type) { 'gpl-3.0' }
|
||||
|
||||
it 'returns the license text' do
|
||||
expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
|
||||
end
|
||||
|
||||
it 'replaces placeholder values' do
|
||||
expect(json_response['content']).to include('My Awesome Project')
|
||||
expect(json_response['content']).to include('Copyright (C) 2016 Anton')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for the gpl-2.0 license' do
|
||||
let(:license_type) { 'gpl-2.0' }
|
||||
|
||||
it 'returns the license text' do
|
||||
expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE')
|
||||
end
|
||||
|
||||
it 'replaces placeholder values' do
|
||||
expect(json_response['content']).to include('My Awesome Project')
|
||||
expect(json_response['content']).to include('Copyright (C) 2016 Anton')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for the apache-2.0 license' do
|
||||
let(:license_type) { 'apache-2.0' }
|
||||
|
||||
it 'returns the license text' do
|
||||
expect(json_response['content']).to include('Apache License')
|
||||
end
|
||||
|
||||
it 'replaces placeholder values' do
|
||||
expect(json_response['content']).to include('Copyright 2016 Anton')
|
||||
end
|
||||
end
|
||||
|
||||
context 'for an uknown license' do
|
||||
let(:license_type) { 'muth-over9000' }
|
||||
|
||||
it 'returns a 404' do
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no :fullname given' do
|
||||
context 'with an authenticated user' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'replaces the copyright owner placeholder with the name of the current user' do
|
||||
get api('/licenses/mit', user)
|
||||
|
||||
expect(json_response['content']).to include("Copyright (c) 2016 #{user.name}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue