(refs #1292)Add new option to disable repository forking

This commit is contained in:
Naoki Takezoe
2016-10-03 15:26:23 +09:00
parent 28c9f8b89a
commit 82b102845f
10 changed files with 192 additions and 115 deletions

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<addColumn tableName="REPOSITORY">
<column name="ALLOW_FORK" type="boolean" nullable="false" defaultValueBoolean="true"/>
</addColumn>
</changeSet>

View File

@@ -15,5 +15,8 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new Version("4.2.1"), new Version("4.2.1"),
new Version("4.3.0"), new Version("4.3.0"),
new Version("4.4.0"), new Version("4.4.0"),
new Version("4.5.0") new Version("4.5.0"),
new Version("4.6.0",
new LiquibaseMigration("update/gitbucket-core_4.6.xml")
)
) )

View File

@@ -14,6 +14,7 @@ import gitbucket.core.util._
import io.github.gitbucket.scalatra.forms._ import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.scalatra.BadRequest
class AccountController extends AccountControllerBase class AccountController extends AccountControllerBase
@@ -355,76 +356,80 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}) })
get("/:owner/:repository/fork")(readableUsersOnly { repository => get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get if(repository.repository.options.allowFork){
val loginUserName = loginAccount.userName val loginAccount = context.loginAccount.get
val groups = getGroupsByUserName(loginUserName) val loginUserName = loginAccount.userName
groups match { val groups = getGroupsByUserName(loginUserName)
case _: List[String] => groups match {
val managerPermissions = groups.map { group => case _: List[String] =>
val members = getGroupMembers(group) val managerPermissions = groups.map { group =>
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }) val members = getGroupMembers(group)
} context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
helper.html.forkrepository( }
repository, helper.html.forkrepository(
(groups zip managerPermissions).toMap repository,
) (groups zip managerPermissions).toMap
case _ => redirect(s"/${loginUserName}") )
} case _ => redirect(s"/${loginUserName}")
}
} else BadRequest
}) })
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
val loginAccount = context.loginAccount.get if(repository.repository.options.allowFork){
val loginUserName = loginAccount.userName val loginAccount = context.loginAccount.get
val accountName = form.accountName val loginUserName = loginAccount.userName
val accountName = form.accountName
LockUtil.lock(s"${accountName}/${repository.name}"){ LockUtil.lock(s"${accountName}/${repository.name}"){
if(getRepository(accountName, repository.name).isDefined || if(getRepository(accountName, repository.name).isDefined ||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){ (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
// redirect to the repository if repository already exists // redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}") redirect(s"/${accountName}/${repository.name}")
} else { } else {
// Insert to the database at first // Insert to the database at first
val originUserName = repository.repository.originUserName.getOrElse(repository.owner) val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
insertRepository( insertRepository(
repositoryName = repository.name, repositoryName = repository.name,
userName = accountName, userName = accountName,
description = repository.repository.description, description = repository.repository.description,
isPrivate = repository.repository.isPrivate, isPrivate = repository.repository.isPrivate,
originRepositoryName = Some(originRepositoryName), originRepositoryName = Some(originRepositoryName),
originUserName = Some(originUserName), originUserName = Some(originUserName),
parentRepositoryName = Some(repository.name), parentRepositoryName = Some(repository.name),
parentUserName = Some(repository.owner) parentUserName = Some(repository.owner)
) )
// Add collaborators for group repository // Add collaborators for group repository
val ownerAccount = getAccountByUserName(accountName).get val ownerAccount = getAccountByUserName(accountName).get
if(ownerAccount.isGroupAccount){ if(ownerAccount.isGroupAccount){
getGroupMembers(accountName).foreach { member => getGroupMembers(accountName).foreach { member =>
addCollaborator(accountName, repository.name, member.userName) addCollaborator(accountName, repository.name, member.userName)
}
} }
// Insert default labels
insertDefaultLabels(accountName, repository.name)
// clone repository actually
JGitUtil.cloneRepository(
getRepositoryDir(repository.owner, repository.name),
getRepositoryDir(accountName, repository.name))
// Create Wiki repository
JGitUtil.cloneRepository(
getWikiRepositoryDir(repository.owner, repository.name),
getWikiRepositoryDir(accountName, repository.name))
// Record activity
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
} }
// Insert default labels
insertDefaultLabels(accountName, repository.name)
// clone repository actually
JGitUtil.cloneRepository(
getRepositoryDir(repository.owner, repository.name),
getRepositoryDir(accountName, repository.name))
// Create Wiki repository
JGitUtil.cloneRepository(
getWikiRepositoryDir(repository.owner, repository.name),
getWikiRepositoryDir(accountName, repository.name))
// Record activity
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
} }
} } else BadRequest
}) })
private def existsAccount: Constraint = new Constraint(){ private def existsAccount: Constraint = new Constraint(){

View File

@@ -35,7 +35,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
externalIssuesUrl: Option[String], externalIssuesUrl: Option[String],
enableWiki: Boolean, enableWiki: Boolean,
allowWikiEditing: Boolean, allowWikiEditing: Boolean,
externalWikiUrl: Option[String] externalWikiUrl: Option[String],
allowFork: Boolean
) )
val optionsForm = mapping( val optionsForm = mapping(
@@ -46,7 +47,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))), "externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))),
"enableWiki" -> trim(label("Enable Wiki" , boolean())), "enableWiki" -> trim(label("Enable Wiki" , boolean())),
"allowWikiEditing" -> trim(label("Allow Wiki Editing" , boolean())), "allowWikiEditing" -> trim(label("Allow Wiki Editing" , boolean())),
"externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))) "externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))),
"allowFork" -> trim(label("Allow Forking" , boolean()))
)(OptionsForm.apply) )(OptionsForm.apply)
// for default branch // for default branch
@@ -111,7 +113,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
form.externalIssuesUrl, form.externalIssuesUrl,
form.enableWiki, form.enableWiki,
form.allowWikiEditing, form.allowWikiEditing,
form.externalWikiUrl form.externalWikiUrl,
form.allowFork
) )
// Change repository name // Change repository name
if(repository.name != form.repositoryName){ if(repository.name != form.repositoryName){

View File

@@ -494,18 +494,20 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/network/members")(referrersOnly { repository => get("/:owner/:repository/network/members")(referrersOnly { repository =>
html.forked( if(repository.repository.options.allowFork) {
getRepository( html.forked(
repository.repository.originUserName.getOrElse(repository.owner), getRepository(
repository.repository.originRepositoryName.getOrElse(repository.name)), repository.repository.originUserName.getOrElse(repository.owner),
getForkedRepositories( repository.repository.originRepositoryName.getOrElse(repository.name)),
repository.repository.originUserName.getOrElse(repository.owner), getForkedRepositories(
repository.repository.originRepositoryName.getOrElse(repository.name)), repository.repository.originUserName.getOrElse(repository.owner),
context.loginAccount match { repository.repository.originRepositoryName.getOrElse(repository.name)),
case None => List() context.loginAccount match {
case account: Option[Account] => getGroupsByUserName(account.get.userName) case None => List()
}, // groups of current user case account: Option[Account] => getGroupsByUserName(account.get.userName)
repository) }, // groups of current user
repository)
} else BadRequest()
}) })
/** /**

View File

@@ -241,7 +241,7 @@ trait WikiControllerBase extends ControllerBase {
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName")) private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean =
repository.repository.allowWikiEditing || ( repository.repository.options.allowWikiEditing || (
hasWritePermission(repository.owner, repository.name, context.loginAccount) hasWritePermission(repository.owner, repository.name, context.loginAccount)
) )

View File

@@ -7,24 +7,62 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
lazy val Repositories = TableQuery[Repositories] lazy val Repositories = TableQuery[Repositories]
class Repositories(tag: Tag) extends Table[Repository](tag, "REPOSITORY") with BasicTemplate { class Repositories(tag: Tag) extends Table[Repository](tag, "REPOSITORY") with BasicTemplate {
val isPrivate = column[Boolean]("PRIVATE") val isPrivate = column[Boolean]("PRIVATE")
val description = column[String]("DESCRIPTION") val description = column[String]("DESCRIPTION")
val defaultBranch = column[String]("DEFAULT_BRANCH") val defaultBranch = column[String]("DEFAULT_BRANCH")
val registeredDate = column[java.util.Date]("REGISTERED_DATE") val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE")
val originUserName = column[String]("ORIGIN_USER_NAME") val originUserName = column[String]("ORIGIN_USER_NAME")
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME") val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
val parentUserName = column[String]("PARENT_USER_NAME") val parentUserName = column[String]("PARENT_USER_NAME")
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME") val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
val enableIssues = column[Boolean]("ENABLE_ISSUES") val enableIssues = column[Boolean]("ENABLE_ISSUES")
val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL") val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL")
val enableWiki = column[Boolean]("ENABLE_WIKI") val enableWiki = column[Boolean]("ENABLE_WIKI")
val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING") val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING")
val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL") val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL")
def * = (userName, repositoryName, isPrivate, description.?, defaultBranch, val allowFork = column[Boolean]("ALLOW_FORK")
registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?,
enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?) <> (Repository.tupled, Repository.unapply) def * = (
(userName, repositoryName, isPrivate, description.?, defaultBranch,
registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?),
(enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?, allowFork)
).shaped <> (
{ case (repository, options) =>
Repository(
repository._1,
repository._2,
repository._3,
repository._4,
repository._5,
repository._6,
repository._7,
repository._8,
repository._9,
repository._10,
repository._11,
repository._12,
RepositoryOptions.tupled.apply(options)
)
}, { (r: Repository) =>
Some(((
r.userName,
r.repositoryName,
r.isPrivate,
r.description,
r.defaultBranch,
r.registeredDate,
r.updatedDate,
r.lastActivityDate,
r.originUserName,
r.originRepositoryName,
r.parentUserName,
r.parentRepositoryName
),(
RepositoryOptions.unapply(r.options).get
)))
})
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
} }
@@ -43,9 +81,14 @@ case class Repository(
originRepositoryName: Option[String], originRepositoryName: Option[String],
parentUserName: Option[String], parentUserName: Option[String],
parentRepositoryName: Option[String], parentRepositoryName: Option[String],
options: RepositoryOptions
)
case class RepositoryOptions(
enableIssues: Boolean, enableIssues: Boolean,
externalIssuesUrl: Option[String], externalIssuesUrl: Option[String],
enableWiki: Boolean, enableWiki: Boolean,
allowWikiEditing: Boolean, allowWikiEditing: Boolean,
externalWikiUrl: Option[String] externalWikiUrl: Option[String],
allowFork: Boolean
) )

View File

@@ -1,7 +1,7 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.model.{Collaborator, Repository, Account} import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
import profile.simple._ import profile.simple._
@@ -37,11 +37,14 @@ trait RepositoryService { self: AccountService =>
originRepositoryName = originRepositoryName, originRepositoryName = originRepositoryName,
parentUserName = parentUserName, parentUserName = parentUserName,
parentRepositoryName = parentRepositoryName, parentRepositoryName = parentRepositoryName,
enableIssues = true, options = RepositoryOptions(
externalIssuesUrl = None, enableIssues = true,
enableWiki = true, externalIssuesUrl = None,
allowWikiEditing = true, enableWiki = true,
externalWikiUrl = None allowWikiEditing = true,
externalWikiUrl = None,
allowFork = true
)
) )
IssueId insert (userName, repositoryName, 0) IssueId insert (userName, repositoryName, 0)
@@ -321,10 +324,11 @@ trait RepositoryService { self: AccountService =>
def saveRepositoryOptions(userName: String, repositoryName: String, def saveRepositoryOptions(userName: String, repositoryName: String,
description: Option[String], isPrivate: Boolean, description: Option[String], isPrivate: Boolean,
enableIssues: Boolean, externalIssuesUrl: Option[String], enableIssues: Boolean, externalIssuesUrl: Option[String],
enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String])(implicit s: Session): Unit = enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String],
allowFork: Boolean)(implicit s: Session): Unit =
Repositories.filter(_.byRepository(userName, repositoryName)) Repositories.filter(_.byRepository(userName, repositoryName))
.map { r => (r.description.?, r.isPrivate, r.enableIssues, r.externalIssuesUrl.?, r.enableWiki, r.allowWikiEditing, r.externalWikiUrl.?, r.updatedDate) } .map { r => (r.description.?, r.isPrivate, r.enableIssues, r.externalIssuesUrl.?, r.enableWiki, r.allowWikiEditing, r.externalWikiUrl.?, r.allowFork, r.updatedDate) }
.update (description, isPrivate, enableIssues, externalIssuesUrl, enableWiki, allowWikiEditing, externalWikiUrl, currentDate) .update (description, isPrivate, enableIssues, externalIssuesUrl, enableWiki, allowWikiEditing, externalWikiUrl, allowFork, currentDate)
def saveRepositoryDefaultBranch(userName: String, repositoryName: String, def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
defaultBranch: String)(implicit s: Session): Unit = defaultBranch: String)(implicit s: Session): Unit =

View File

@@ -27,24 +27,26 @@
@menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length) @menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length)
@menuitem("/tags", "tags", "Tags", "tag", repository.tags.length) @menuitem("/tags", "tags", "Tags", "tag", repository.tags.length)
} }
@if(repository.repository.enableIssues) { @if(repository.repository.options.enableIssues) {
@menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount) @menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount)
@menuitem("/pulls", "pulls", "Pull Requests", "git-pull-request", repository.pullCount) @menuitem("/pulls", "pulls", "Pull Requests", "git-pull-request", repository.pullCount)
@menuitem("/issues/labels", "labels", "Labels", "tag") @menuitem("/issues/labels", "labels", "Labels", "tag")
@menuitem("/issues/milestones", "milestones", "Milestones", "milestone") @menuitem("/issues/milestones", "milestones", "Milestones", "milestone")
} else { } else {
@repository.repository.externalIssuesUrl.map { externalIssuesUrl => @repository.repository.options.externalIssuesUrl.map { externalIssuesUrl =>
@menuitem(externalIssuesUrl, "issues", "Issues", "issue-opened") @menuitem(externalIssuesUrl, "issues", "Issues", "issue-opened")
} }
} }
@if(repository.repository.enableWiki) { @if(repository.repository.options.enableWiki) {
@menuitem("/wiki", "wiki", "Wiki", "book") @menuitem("/wiki", "wiki", "Wiki", "book")
} else { } else {
@repository.repository.externalWikiUrl.map { externalWikiUrl => @repository.repository.options.externalWikiUrl.map { externalWikiUrl =>
@menuitem(externalWikiUrl, "wiki", "Wiki", "book") @menuitem(externalWikiUrl, "wiki", "Wiki", "book")
} }
} }
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount) @if(repository.repository.options.allowFork) {
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
}
@if(context.loginAccount.isDefined && (context.loginAccount.get.isAdmin || repository.managers.contains(context.loginAccount.get.userName))){ @if(context.loginAccount.isDefined && (context.loginAccount.get.isAdmin || repository.managers.contains(context.loginAccount.get.userName))){
@menuitem("/settings", "settings", "Settings", "tools") @menuitem("/settings", "settings", "Settings", "tools")
} }

View File

@@ -46,7 +46,7 @@
<div class="panel-body"> <div class="panel-body">
<fieldset class="form-group"> <fieldset class="form-group">
<label class="checkbox" for="enableIssues"> <label class="checkbox" for="enableIssues">
<input type="checkbox" id="enableIssues" name="enableIssues"@if(repository.repository.enableIssues){ checked}/> <input type="checkbox" id="enableIssues" name="enableIssues"@if(repository.repository.options.enableIssues){ checked}/>
Issues<br> Issues<br>
<div class="normal muted"> <div class="normal muted">
Provides Lightweight issue tracking integrated with this repository. Add issues to milestones, label issues, and close & reference issues from commit messages. Provides Lightweight issue tracking integrated with this repository. Add issues to milestones, label issues, and close & reference issues from commit messages.
@@ -55,24 +55,33 @@
<label for="externalIssuesUrl" class="strong">External URL: <label for="externalIssuesUrl" class="strong">External URL:
<span class="normal muted">(Put if you have the external issue tracking system for this project)</span> <span class="normal muted">(Put if you have the external issue tracking system for this project)</span>
</label> </label>
<input type="text" class="form-control" id="externalIssuesUrl" name="externalIssuesUrl" value="@repository.repository.externalIssuesUrl"/> <input type="text" class="form-control" id="externalIssuesUrl" name="externalIssuesUrl" value="@repository.repository.options.externalIssuesUrl"/>
</fieldset> </fieldset>
<fieldset class="form-group margin"> <fieldset class="form-group">
<label class="checkbox" for="enableWiki"> <label class="checkbox" for="enableWiki">
<input type="checkbox" id="enableWiki" name="enableWiki"@if(repository.repository.enableWiki){ checked}/> <input type="checkbox" id="enableWiki" name="enableWiki"@if(repository.repository.options.enableWiki){ checked}/>
Wiki<br> Wiki<br>
<div class="normal muted"> <div class="normal muted">
Provides a simple solution to manage documents. All users who can look this repository can read and collaborators can edit pages. Provides a simple solution to manage documents. All users who can look this repository can read and collaborators can edit pages.
</div> </div>
</label> </label>
<label class="checkbox" for="allowWikiEditing"> <label class="checkbox" for="allowWikiEditing">
<input type="checkbox" id="allowWikiEditing" name="allowWikiEditing"@if(repository.repository.allowWikiEditing){ checked}/> <input type="checkbox" id="allowWikiEditing" name="allowWikiEditing"@if(repository.repository.options.allowWikiEditing){ checked}/>
Allow read-only users to edit Wiki pages<br> Allow read-only users to edit Wiki pages<br>
</label> </label>
<label for="externalWikiUrl" class="strong">External URL: <label for="externalWikiUrl" class="strong">External URL:
<span class="normal muted">(Put if you have the external Wiki for this project)</span> <span class="normal muted">(Put if you have the external Wiki for this project)</span>
</label> </label>
<input type="text" class="form-control" id="externalWikiUrl" name="externalWikiUrl" value="@repository.repository.externalWikiUrl"/> <input type="text" class="form-control" id="externalWikiUrl" name="externalWikiUrl" value="@repository.repository.options.externalWikiUrl"/>
</fieldset>
<fieldset class="form-group">
<label class="checkbox" for="allowFork">
<input type="checkbox" id="allowFork" name="allowFork"@if(repository.repository.options.allowFork){ checked}/>
Forks<br>
<div class="normal muted">
Allow repository forking to users who can access this repository.
</div>
</label>
</fieldset> </fieldset>
</div> </div>
</div> </div>