diff --git a/src/main/resources/update/gitbucket-core_4.44.xml b/src/main/resources/update/gitbucket-core_4.44.xml
new file mode 100644
index 000000000..049740185
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.44.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+ UPDATE PROTECTED_BRANCH SET REQUIRED_STATUS_CHECK = TRUE
+ WHERE EXISTS (SELECT * FROM PROTECTED_BRANCH_REQUIRE_CONTEXT
+ WHERE PROTECTED_BRANCH.USER_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.USER_NAME
+ AND PROTECTED_BRANCH.REPOSITORY_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.REPOSITORY_NAME
+ AND PROTECTED_BRANCH.BRANCH = PROTECTED_BRANCH_REQUIRE_CONTEXT.BRANCH)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
index be7c9de91..128c1a83d 100644
--- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
+++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
@@ -120,7 +120,8 @@ object GitBucketCoreModule
new Version("4.41.0"),
new Version("4.42.0", new LiquibaseMigration("update/gitbucket-core_4.42.xml")),
new Version("4.42.1"),
- new Version("4.43.0")
+ new Version("4.43.0"),
+ new Version("4.44.0", new LiquibaseMigration("update/gitbucket-core_4.44.xml"))
) {
java.util.logging.Logger.getLogger("liquibase").setLevel(Level.SEVERE)
}
diff --git a/src/main/scala/gitbucket/core/api/ApiBranch.scala b/src/main/scala/gitbucket/core/api/ApiBranch.scala
index 629b2ac51..a6754e67c 100644
--- a/src/main/scala/gitbucket/core/api/ApiBranch.scala
+++ b/src/main/scala/gitbucket/core/api/ApiBranch.scala
@@ -6,7 +6,7 @@ import gitbucket.core.util.RepositoryName
* https://developer.github.com/v3/repos/#get-branch
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
*/
-case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)(
+case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtectionResponse)(
repositoryName: RepositoryName
) extends FieldSerializable {
val _links =
diff --git a/src/main/scala/gitbucket/core/api/ApiBranchProtectionRequest.scala b/src/main/scala/gitbucket/core/api/ApiBranchProtectionRequest.scala
new file mode 100644
index 000000000..9d00f5a58
--- /dev/null
+++ b/src/main/scala/gitbucket/core/api/ApiBranchProtectionRequest.scala
@@ -0,0 +1,21 @@
+package gitbucket.core.api
+
+/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
+case class ApiBranchProtectionRequest(
+ enabled: Boolean,
+ required_status_checks: Option[ApiBranchProtectionRequest.Status],
+ restrictions: Option[ApiBranchProtectionRequest.Restrictions],
+ enforce_admins: Option[Boolean]
+)
+
+object ApiBranchProtectionRequest {
+
+ /** form for enabling-and-disabling-branch-protection */
+ case class EnablingAndDisabling(protection: ApiBranchProtectionRequest)
+
+ case class Status(
+ contexts: Seq[String]
+ )
+
+ case class Restrictions(users: Seq[String])
+}
diff --git a/src/main/scala/gitbucket/core/api/ApiBranchProtection.scala b/src/main/scala/gitbucket/core/api/ApiBranchProtectionResponse.scala
similarity index 65%
rename from src/main/scala/gitbucket/core/api/ApiBranchProtection.scala
rename to src/main/scala/gitbucket/core/api/ApiBranchProtectionResponse.scala
index 93d36aed4..74443bb5b 100644
--- a/src/main/scala/gitbucket/core/api/ApiBranchProtection.scala
+++ b/src/main/scala/gitbucket/core/api/ApiBranchProtectionResponse.scala
@@ -4,55 +4,68 @@ import gitbucket.core.service.ProtectedBranchService
import org.json4s._
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
-case class ApiBranchProtection(
+case class ApiBranchProtectionResponse(
url: Option[ApiPath], // for output
enabled: Boolean,
- required_status_checks: Option[ApiBranchProtection.Status]
+ required_status_checks: Option[ApiBranchProtectionResponse.Status],
+ restrictions: Option[ApiBranchProtectionResponse.Restrictions],
+ enforce_admins: Option[ApiBranchProtectionResponse.EnforceAdmins]
) {
- def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
+ def status: ApiBranchProtectionResponse.Status =
+ required_status_checks.getOrElse(ApiBranchProtectionResponse.statusNone)
}
-object ApiBranchProtection {
+object ApiBranchProtectionResponse {
- /** form for enabling-and-disabling-branch-protection */
- case class EnablingAndDisabling(protection: ApiBranchProtection)
+ case class EnforceAdmins(enabled: Boolean)
- def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection =
- ApiBranchProtection(
+// /** form for enabling-and-disabling-branch-protection */
+// case class EnablingAndDisabling(protection: ApiBranchProtectionResponse)
+
+ def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtectionResponse =
+ ApiBranchProtectionResponse(
url = Some(
ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection"
)
),
enabled = info.enabled,
- required_status_checks = Some(
+ required_status_checks = info.contexts.map { contexts =>
Status(
Some(
ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks"
)
),
- EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators),
- info.contexts,
+ EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.enforceAdmins),
+ contexts,
Some(
ApiPath(
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks/contexts"
)
)
)
- )
+ },
+ restrictions = info.restrictionsUsers.map { restrictionsUsers =>
+ Restrictions(restrictionsUsers)
+ },
+ enforce_admins = if (info.enabled) Some(EnforceAdmins(info.enforceAdmins)) else None
)
- val statusNone = Status(None, Off, Seq.empty, None)
+
+ val statusNone: Status = Status(None, Off, Seq.empty, None)
+
case class Status(
url: Option[ApiPath], // for output
enforcement_level: EnforcementLevel,
contexts: Seq[String],
contexts_url: Option[ApiPath] // for output
)
+
sealed class EnforcementLevel(val name: String)
case object Off extends EnforcementLevel("off")
case object NonAdmins extends EnforcementLevel("non_admins")
case object Everyone extends EnforcementLevel("everyone")
+
object EnforcementLevel {
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel =
if (enabled) {
@@ -66,6 +79,8 @@ object ApiBranchProtection {
}
}
+ case class Restrictions(users: Seq[String])
+
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] =
new CustomSerializer[EnforcementLevel](format =>
(
diff --git a/src/main/scala/gitbucket/core/api/JsonFormat.scala b/src/main/scala/gitbucket/core/api/JsonFormat.scala
index ee3453420..12437eb0a 100644
--- a/src/main/scala/gitbucket/core/api/JsonFormat.scala
+++ b/src/main/scala/gitbucket/core/api/JsonFormat.scala
@@ -44,7 +44,7 @@ object JsonFormat {
FieldSerializer[ApiCommits.File]() +
FieldSerializer[ApiRelease]() +
FieldSerializer[ApiReleaseAsset]() +
- ApiBranchProtection.enforcementLevelSerializer
+ ApiBranchProtectionResponse.enforcementLevelSerializer
def apiPathSerializer(c: Context) =
new CustomSerializer[ApiPath](_ =>
diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala
index 1fa3e49fa..9c2758074 100644
--- a/src/main/scala/gitbucket/core/controller/IndexController.scala
+++ b/src/main/scala/gitbucket/core/controller/IndexController.scala
@@ -261,11 +261,22 @@ trait IndexControllerBase extends ControllerBase {
/**
* JSON API for checking user or group existence.
+ *
* Returns a single string which is any of "group", "user" or "".
+ * Additionally, check whether the user is writable to the repository
+ * if "owner" and "repository" are given,
*/
post("/_user/existence")(usersOnly {
getAccountByUserNameIgnoreCase(params("userName")).map { account =>
- if (account.isGroupAccount) "group" else "user"
+ if (!account.isGroupAccount && params.get("repository").isDefined && params.get("owner").isDefined) {
+ getRepository(params("owner"), params("repository"))
+ .collect {
+ case repository if isWritable(repository.repository, Some(account)) => "user"
+ }
+ .getOrElse("")
+ } else {
+ if (account.isGroupAccount) "group" else "user"
+ }
} getOrElse ""
})
diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
index b598f1687..6a46c5338 100644
--- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
@@ -203,7 +203,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
if (!repository.branchList.contains(branch)) {
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
} else {
- val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
+ val protection = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
val lastWeeks = getRecentStatusContexts(
repository.owner,
repository.name,
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryBranchControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryBranchControllerBase.scala
index 0d7d4c236..f1f06d41a 100644
--- a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryBranchControllerBase.scala
+++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryBranchControllerBase.scala
@@ -5,7 +5,7 @@ import gitbucket.core.service.{AccountService, ProtectedBranchService, Repositor
import gitbucket.core.util.*
import gitbucket.core.util.Directory.*
import gitbucket.core.util.Implicits.*
-import gitbucket.core.util.JGitUtil.getBranchesNoMergeInfo
+import gitbucket.core.util.JGitUtil.{getBranchesNoMergeInfo, processTree}
import org.eclipse.jgit.api.Git
import org.scalatra.NoContent
@@ -43,7 +43,9 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
} yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat(
- ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
+ ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtectionResponse(protection))(
+ RepositoryName(repository)
+ )
)
}) getOrElse NotFound()
}
@@ -58,7 +60,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat(
- ApiBranchProtection(protection)
+ ApiBranchProtectionResponse(protection)
)
} else { NotFound() }
})
@@ -138,7 +140,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
if (repository.branchList.contains(branch)) {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat(
- ApiBranchProtection(protection).required_status_checks
+ ApiBranchProtectionResponse(protection).required_status_checks
)
} else { NotFound() }
})
@@ -262,7 +264,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
(for {
branch <- params.get("splat") if repository.branchList.contains(branch)
- protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
+ protection <- extractFromJsonBody[ApiBranchProtectionRequest.EnablingAndDisabling].map(_.protection)
br <- getBranchesNoMergeInfo(git).find(_.name == branch)
} yield {
if (protection.enabled) {
@@ -270,13 +272,17 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
repository.owner,
repository.name,
branch,
- protection.status.enforcement_level == ApiBranchProtection.Everyone,
- protection.status.contexts
+ protection.enforce_admins.getOrElse(false),
+ protection.required_status_checks.isDefined,
+ protection.required_status_checks.map(_.contexts).getOrElse(Nil),
+ protection.restrictions.isDefined,
+ protection.restrictions.map(_.users).getOrElse(Nil)
)
} else {
disableBranchProtection(repository.owner, repository.name, branch)
}
- JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
+ val response = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
+ JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), response)(RepositoryName(repository)))
}) getOrElse NotFound()
}
})
diff --git a/src/main/scala/gitbucket/core/model/ProtectedBranch.scala b/src/main/scala/gitbucket/core/model/ProtectedBranch.scala
index 09bc6002b..60ad90198 100644
--- a/src/main/scala/gitbucket/core/model/ProtectedBranch.scala
+++ b/src/main/scala/gitbucket/core/model/ProtectedBranch.scala
@@ -1,16 +1,18 @@
package gitbucket.core.model
trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
- import profile.api._
- import self._
+ import profile.api.*
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
- val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
- def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
- def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
+ val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN") // enforceAdmins
+ val requiredStatusCheck = column[Boolean]("REQUIRED_STATUS_CHECK")
+ val restrictions = column[Boolean]("RESTRICTIONS")
+ def * =
+ (userName, repositoryName, branch, statusCheckAdmin, requiredStatusCheck, restrictions).mapTo[ProtectedBranch]
+ def byPrimaryKey(userName: String, repositoryName: String, branch: String): Rep[Boolean] =
byBranch(userName, repositoryName, branch)
- def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
+ def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]): Rep[Boolean] =
byBranch(userName, repositoryName, branch)
}
@@ -22,8 +24,27 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
def * =
(userName, repositoryName, branch, context).mapTo[ProtectedBranchContext]
}
+
+ lazy val ProtectedBranchRestrictions = TableQuery[ProtectedBranchRestrictions]
+ class ProtectedBranchRestrictions(tag: Tag)
+ extends Table[ProtectedBranchRestriction](tag, "PROTECTED_BRANCH_RESTRICTION")
+ with BranchTemplate {
+ val allowedUser = column[String]("ALLOWED_USER")
+ def * = (userName, repositoryName, branch, allowedUser).mapTo[ProtectedBranchRestriction]
+ def byPrimaryKey(userName: String, repositoryName: String, branch: String, allowedUser: String): Rep[Boolean] =
+ this.userName === userName.bind && this.repositoryName === repositoryName.bind && this.branch === branch.bind && this.allowedUser === allowedUser.bind
+ }
}
-case class ProtectedBranch(userName: String, repositoryName: String, branch: String, statusCheckAdmin: Boolean)
+case class ProtectedBranch(
+ userName: String,
+ repositoryName: String,
+ branch: String,
+ enforceAdmins: Boolean,
+ requiredStatusCheck: Boolean,
+ restrictions: Boolean
+)
case class ProtectedBranchContext(userName: String, repositoryName: String, branch: String, context: String)
+
+case class ProtectedBranchRestriction(userName: String, repositoryName: String, branch: String, allowedUser: String)
diff --git a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala
index 326ca424e..cb9ee929b 100644
--- a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala
+++ b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala
@@ -1,9 +1,10 @@
package gitbucket.core.service
-import gitbucket.core.model.{Session => _, _}
import gitbucket.core.plugin.ReceiveHook
-import gitbucket.core.model.Profile._
-import gitbucket.core.model.Profile.profile.blockingApi._
+import gitbucket.core.model.Profile.*
+import gitbucket.core.model.Profile.profile.blockingApi.*
+import gitbucket.core.model.{CommitState, ProtectedBranch, ProtectedBranchContext, ProtectedBranchRestriction, Role}
+import gitbucket.core.util.SyntaxSugars.*
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
trait ProtectedBranchService {
@@ -13,17 +14,27 @@ trait ProtectedBranchService {
): Option[ProtectedBranchInfo] =
ProtectedBranches
.joinLeft(ProtectedBranchContexts)
- .on { case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) }
- .map { case (pb, c) => pb -> c.map(_.context) }
+ .on { case pb ~ c => pb.byBranch(c.userName, c.repositoryName, c.branch) }
+ .joinLeft(ProtectedBranchRestrictions)
+ .on { case pb ~ c ~ r => pb.byBranch(r.userName, r.repositoryName, r.branch) }
+ .map { case pb ~ c ~ r => pb -> (c.map(_.context), r.map(_.allowedUser)) }
.filter(_._1.byPrimaryKey(owner, repository, branch))
.list
.groupBy(_._1)
.headOption
- .map { p =>
- p._1 -> p._2.flatMap(_._2)
+ .map { (p: (ProtectedBranch, List[(ProtectedBranch, (Option[String], Option[String]))])) =>
+ p._1 -> (p._2.flatMap(_._2._1), p._2.flatMap(_._2._2))
}
- .map { case (t1, contexts) =>
- new ProtectedBranchInfo(t1.userName, t1.repositoryName, t1.branch, true, contexts, t1.statusCheckAdmin)
+ .map { case (t1, (contexts, users)) =>
+ new ProtectedBranchInfo(
+ t1.userName,
+ t1.repositoryName,
+ t1.branch,
+ true,
+ if (t1.requiredStatusCheck) Some(contexts) else None,
+ t1.enforceAdmins,
+ if (t1.restrictions) Some(users) else None
+ )
}
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit
@@ -40,19 +51,32 @@ trait ProtectedBranchService {
owner: String,
repository: String,
branch: String,
- includeAdministrators: Boolean,
- contexts: Seq[String]
+ enforceAdmins: Boolean,
+ requiredStatusCheck: Boolean,
+ contexts: Seq[String],
+ restrictions: Boolean,
+ restrictionsUsers: Seq[String]
)(implicit session: Session): Unit = {
disableBranchProtection(owner, repository, branch)
- ProtectedBranches.insert(new ProtectedBranch(owner, repository, branch, includeAdministrators && contexts.nonEmpty))
- contexts.map { context =>
- ProtectedBranchContexts.insert(new ProtectedBranchContext(owner, repository, branch, context))
+ ProtectedBranches.insert(
+ ProtectedBranch(owner, repository, branch, enforceAdmins, requiredStatusCheck, restrictions)
+ )
+
+ if (restrictions) {
+ restrictionsUsers.foreach { user =>
+ ProtectedBranchRestrictions.insert(ProtectedBranchRestriction(owner, repository, branch, user))
+ }
+ }
+
+ if (requiredStatusCheck) {
+ contexts.foreach { context =>
+ ProtectedBranchContexts.insert(ProtectedBranchContext(owner, repository, branch, context))
+ }
}
}
def disableBranchProtection(owner: String, repository: String, branch: String)(implicit session: Session): Unit =
ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete
-
}
object ProtectedBranchService {
@@ -101,6 +125,7 @@ object ProtectedBranchService {
)
}
} else {
+ println("-> else")
None
}
}
@@ -117,12 +142,16 @@ object ProtectedBranchService {
* When enabled, commits must first be pushed to another branch,
* then merged or pushed directly to test after status checks have passed.
*/
- contexts: Seq[String],
+ contexts: Option[Seq[String]],
/**
* Include administrators
* Enforce required status checks for repository administrators.
*/
- includeAdministrators: Boolean
+ enforceAdmins: Boolean,
+ /**
+ * Users who can push to the branch.
+ */
+ restrictionsUsers: Option[Seq[String]]
) extends AccountService
with RepositoryService
with CommitStatusService {
@@ -148,42 +177,66 @@ object ProtectedBranchService {
session: Session
): Option[String] = {
if (enabled) {
- command.getType() match {
+ command.getType match {
case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
Some("Cannot force-push to a protected branch")
+ case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if !isPushAllowed(pusher) =>
+ Some("You do not have permission to push to this branch")
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
unSuccessedContexts(command.getNewId.name) match {
- case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
- case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
- case _ => None
+ case s if s.sizeIs == 1 => Some(s"""Required status check "${s.head}" is expected""")
+ case s if s.sizeIs >= 1 =>
+ Some(s"${s.size} of ${contexts.map(_.size).getOrElse(0)} required status checks are expected")
+ case _ => None
}
case ReceiveCommand.Type.DELETE =>
- Some("Cannot delete a protected branch")
+ Some("You do not have permission to push to this branch")
case _ => None
}
} else {
None
}
}
- def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] =
- if (contexts.isEmpty) {
- Set.empty
- } else {
- contexts.toSet -- getCommitStatuses(owner, repository, sha1)
- .filter(_.state == CommitState.SUCCESS)
- .map(_.context)
- .toSet
+
+ def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = {
+ contexts match {
+ case None => Set.empty
+ case Some(x) if x.isEmpty => Set.empty
+ case Some(x) =>
+ x.toSet -- getCommitStatuses(owner, repository, sha1)
+ .filter(_.state == CommitState.SUCCESS)
+ .map(_.context)
+ .toSet
}
+ }
+
def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match {
- case _ if !enabled => false
- case _ if contexts.isEmpty => false
- case _ if includeAdministrators => true
- case p if isAdministrator(p) => false
- case _ => true
+ case _ if !enabled => false
+ case _ if contexts.isEmpty => false
+ case _ if enforceAdmins => true
+ case p if isAdministrator(p) => false
+ case _ => true
+ }
+
+ def isPushAllowed(pusher: String)(implicit session: Session): Boolean = pusher match {
+ case _ if !enabled || restrictionsUsers.isEmpty => true
+ case _ if restrictionsUsers.get.contains(pusher) => true
+ case p if isAdministrator(p) && enforceAdmins => false
+ case _ => false
}
}
+
object ProtectedBranchInfo {
- def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo =
- ProtectedBranchInfo(owner, repository, branch, false, Nil, false)
+ def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo = {
+ ProtectedBranchInfo(
+ owner,
+ repository,
+ branch,
+ enabled = false,
+ contexts = None,
+ enforceAdmins = false,
+ restrictionsUsers = None
+ )
+ }
}
}
diff --git a/src/main/scala/gitbucket/core/service/PullRequestService.scala b/src/main/scala/gitbucket/core/service/PullRequestService.scala
index 49e471945..08348cedf 100644
--- a/src/main/scala/gitbucket/core/service/PullRequestService.scala
+++ b/src/main/scala/gitbucket/core/service/PullRequestService.scala
@@ -653,18 +653,18 @@ object PullRequestService {
commitIdTo: String
) {
- val hasConflict = conflictMessage.isDefined
+ val hasConflict: Boolean = conflictMessage.isDefined
val statuses: List[CommitStatus] =
- commitStatuses ++ (branchProtection.contexts.toSet -- commitStatuses.map(_.context).toSet)
+ commitStatuses ++ (branchProtection.contexts.getOrElse(Nil).toSet -- commitStatuses.map(_.context).toSet)
.map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
- val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context =>
- statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS)
- )
- val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
+ val hasRequiredStatusProblem: Boolean = needStatusCheck && branchProtection.contexts
+ .getOrElse(Nil)
+ .exists(context => !statuses.find(_.context == context).map(_.state).contains(CommitState.SUCCESS))
+ val hasProblem: Boolean = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
statuses.map(_.state).toSet
) != CommitState.SUCCESS)
- val canUpdate = branchIsOutOfDate && !hasConflict
- val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
+ val canUpdate: Boolean = branchIsOutOfDate && !hasConflict
+ val canMerge: Boolean = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
lazy val commitStateSummary: (CommitState, String) = {
val stateMap = statuses.groupBy(_.state)
val state = CommitState.combine(stateMap.keySet)
@@ -672,8 +672,8 @@ object PullRequestService {
state -> summary
}
lazy val statusesAndRequired: List[(CommitStatus, Boolean)] = statuses.map { s =>
- s -> branchProtection.contexts.contains(s.context)
+ s -> branchProtection.contexts.getOrElse(Nil).contains(s.context)
}
- lazy val isAllSuccess = commitStateSummary._1 == CommitState.SUCCESS
+ lazy val isAllSuccess: Boolean = commitStateSummary._1 == CommitState.SUCCESS
}
}
diff --git a/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala b/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala
index dd38a0023..7fe17150e 100644
--- a/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala
+++ b/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala
@@ -1,7 +1,7 @@
package gitbucket.core.service
import gitbucket.core.api.JsonFormat
import gitbucket.core.model.{Account, WebHook}
-import gitbucket.core.model.Profile.profile.blockingApi._
+import gitbucket.core.model.Profile.profile.blockingApi.*
import gitbucket.core.model.activity.{CloseIssueInfo, PushInfo}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService.SystemSettings
@@ -11,14 +11,14 @@ import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.{JGitUtil, LockUtil}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
-import org.eclipse.jgit.lib._
+import org.eclipse.jgit.lib.*
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
import scala.util.Using
trait RepositoryCommitFileService {
self: AccountService & ActivityService & IssuesService & PullRequestService & WebHookPullRequestService &
- RepositoryService =>
+ RepositoryService & ProtectedBranchService =>
/**
* Create multiple files by callback function.
@@ -92,10 +92,10 @@ trait RepositoryCommitFileService {
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
val newPath = newFileName.map { newFileName =>
- if (path.length == 0) newFileName else s"${path}/${newFileName}"
+ if (path.isEmpty) newFileName else s"${path}/${newFileName}"
}
val oldPath = oldFileName.map { oldFileName =>
- if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
+ if (path.isEmpty) oldFileName else s"${path}/${oldFileName}"
}
_createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
@@ -139,7 +139,6 @@ trait RepositoryCommitFileService {
)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, R)] = {
-
LockUtil.lock(s"${repository.owner}/${repository.name}") {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val builder = DirCache.newInCore.builder()
@@ -168,7 +167,14 @@ trait RepositoryCommitFileService {
// call pre-commit hook
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
- hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false)
+ hook.preReceive(
+ repository.owner,
+ repository.name,
+ receivePack,
+ receiveCommand,
+ pusherAccount.userName,
+ mergePullRequest = false
+ )
}.headOption
error match {
@@ -194,7 +200,8 @@ trait RepositoryCommitFileService {
// record activity
updateLastActivityDate(repository.owner, repository.name)
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
- val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
+ val pushInfo =
+ PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
recordActivity(pushInfo)
// create issue comment by commit message
@@ -221,7 +228,14 @@ trait RepositoryCommitFileService {
// call post-commit hook
PluginRegistry().getReceiveHooks.foreach { hook =>
- hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
+ hook.postReceive(
+ repository.owner,
+ repository.name,
+ receivePack,
+ receiveCommand,
+ committerName,
+ mergePullRequest = false
+ )
}
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala
index 5ba21e2b1..e0df0a3bb 100644
--- a/src/main/scala/gitbucket/core/service/RepositoryService.scala
+++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala
@@ -654,11 +654,11 @@ trait RepositoryService {
def hasOwnerRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
loginAccount match {
- case Some(a) if (a.isAdmin) => true
- case Some(a) if (a.userName == owner) => true
- case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
- case Some(a) if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName)) => true
- case _ => false
+ case Some(a) if a.isAdmin => true
+ case Some(a) if a.userName == owner => true
+ case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
+ case Some(a) if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName) => true
+ case _ => false
}
}
@@ -666,11 +666,11 @@ trait RepositoryService {
s: Session
): Boolean = {
loginAccount match {
- case Some(a) if (a.isAdmin) => true
- case Some(a) if (a.userName == owner) => true
- case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
+ case Some(a) if a.isAdmin => true
+ case Some(a) if a.userName == owner => true
+ case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
case Some(a)
- if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName)) =>
+ if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName) =>
true
case _ => false
}
@@ -678,12 +678,12 @@ trait RepositoryService {
def hasGuestRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
loginAccount match {
- case Some(a) if (a.isAdmin) => true
- case Some(a) if (a.userName == owner) => true
- case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
+ case Some(a) if a.isAdmin => true
+ case Some(a) if a.userName == owner => true
+ case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
case Some(a)
- if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
- .contains(a.userName)) =>
+ if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
+ .contains(a.userName) =>
true
case _ => false
}
@@ -694,17 +694,29 @@ trait RepositoryService {
true
} else {
loginAccount match {
- case Some(x) if (x.isAdmin) => true
- case Some(x) if (repository.userName == x.userName) => true
- case Some(x) if (getGroupMembers(repository.userName).exists(_.userName == x.userName)) => true
- case Some(x)
- if (getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) =>
+ case Some(x) if x.isAdmin => true
+ case Some(x) if repository.userName == x.userName => true
+ case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
+ case Some(x) if getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName) =>
true
case _ => false
}
}
}
+ def isWritable(repository: Repository, loginAccount: Option[Account])(implicit s: Session): Boolean = {
+ loginAccount match {
+ case Some(x) if x.isAdmin => true
+ case Some(x) if repository.userName == x.userName => true
+ case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
+ case Some(x)
+ if getCollaboratorUserNames(repository.userName, repository.repositoryName, Seq(Role.ADMIN, Role.DEVELOPER))
+ .contains(x.userName) =>
+ true
+ case _ => false
+ }
+ }
+
private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
Query(Repositories.filter { t =>
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala
index a971fa46d..06405add4 100644
--- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala
+++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala
@@ -9,11 +9,11 @@ import gitbucket.core.api.JsonFormat.Context
import gitbucket.core.model.WebHook
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.IssuesService.IssueSearchCondition
-import gitbucket.core.service.WebHookService._
-import gitbucket.core.service._
-import gitbucket.core.util.Implicits._
-import gitbucket.core.util._
-import gitbucket.core.model.Profile.profile.blockingApi._
+import gitbucket.core.service.WebHookService.*
+import gitbucket.core.service.*
+import gitbucket.core.util.Implicits.*
+import gitbucket.core.util.*
+import gitbucket.core.model.Profile.profile.blockingApi.*
import gitbucket.core.model.activity.{
BaseActivityInfo,
CloseIssueInfo,
@@ -33,9 +33,9 @@ import gitbucket.core.servlet.Database
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.http.server.GitServlet
-import org.eclipse.jgit.lib._
-import org.eclipse.jgit.transport._
-import org.eclipse.jgit.transport.resolver._
+import org.eclipse.jgit.lib.*
+import org.eclipse.jgit.transport.*
+import org.eclipse.jgit.transport.resolver.*
import org.slf4j.LoggerFactory
import javax.servlet.ServletConfig
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
@@ -43,7 +43,7 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.internal.storage.file.FileRepository
import org.json4s.Formats
import org.json4s.convertToJsonInput
-import org.json4s.jackson.Serialization._
+import org.json4s.jackson.Serialization.*
/**
* Provides Git repository via HTTP.
@@ -117,7 +117,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
GitLfs.BatchResponseObject(
requestObject.oid,
requestObject.size,
- true,
+ authenticated = true,
GitLfs.Actions(
upload = Some(
GitLfs.Action(
@@ -138,7 +138,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
GitLfs.BatchResponseObject(
requestObject.oid,
requestObject.size,
- true,
+ authenticated = true,
GitLfs.Actions(
download = Some(
GitLfs.Action(
@@ -223,7 +223,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
}
}
-import scala.jdk.CollectionConverters._
+import scala.jdk.CollectionConverters.*
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
extends PostReceiveHook
@@ -242,6 +242,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
with WebHookPullRequestReviewCommentService
with CommitsService
with SystemSettingsService
+ with ProtectedBranchService
with RequestCache {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
@@ -253,7 +254,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
commands.asScala.foreach { command =>
// call pre-commit hook
PluginRegistry().getReceiveHooks
- .flatMap(_.preReceive(owner, repository, receivePack, command, pusher, false))
+ .flatMap(_.preReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
.headOption
.foreach { error =>
command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error)
@@ -428,8 +429,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
repositoryInfo,
newCommits,
ownerAccount,
- newId = command.getNewId(),
- oldId = command.getOldId()
+ newId = command.getNewId,
+ oldId = command.getOldId
)
}
}
@@ -453,7 +454,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
// call post-commit hook
PluginRegistry().getReceiveHooks
- .foreach(_.postReceive(owner, repository, receivePack, command, pusher, false))
+ .foreach(_.postReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
}
}
// update repository last modified time.
diff --git a/src/main/scala/gitbucket/core/util/Authenticator.scala b/src/main/scala/gitbucket/core/util/Authenticator.scala
index abd4eb5bc..ba838d27f 100644
--- a/src/main/scala/gitbucket/core/util/Authenticator.scala
+++ b/src/main/scala/gitbucket/core/util/Authenticator.scala
@@ -15,9 +15,9 @@ trait OneselfAuthenticator { self: ControllerBase =>
private def authenticate(action: => Any) = {
context.loginAccount match {
- case Some(x) if (x.isAdmin) => action
- case Some(x) if (request.paths(0) == x.userName) => action
- case _ => Unauthorized()
+ case Some(x) if x.isAdmin => action
+ case Some(x) if request.paths(0) == x.userName => action
+ case _ => Unauthorized()
}
}
}
@@ -26,7 +26,7 @@ trait OneselfAuthenticator { self: ControllerBase =>
* Allows only the repository owner and administrators.
*/
trait OwnerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
- protected def ownerOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
+ protected def ownerOnly(action: RepositoryInfo => Any) = { authenticate(action) }
protected def ownerOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
private def authenticate(action: (RepositoryInfo) => Any) = {
@@ -34,14 +34,14 @@ trait OwnerAuthenticator { self: ControllerBase & RepositoryService & AccountSer
val repoName = params("repository")
getRepository(userName, repoName).map { repository =>
context.loginAccount match {
- case Some(x) if (x.isAdmin) => action(repository)
- case Some(x) if (repository.owner == x.userName) => action(repository)
+ case Some(x) if x.isAdmin => action(repository)
+ case Some(x) if repository.owner == x.userName => action(repository)
// TODO Repository management is allowed for only group managers?
- case Some(x) if (getGroupMembers(repository.owner).exists { m =>
+ case Some(x) if getGroupMembers(repository.owner).exists { m =>
m.userName == x.userName && m.isManager
- }) =>
+ } =>
action(repository)
- case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
+ case Some(x) if getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName) =>
action(repository)
case _ => Unauthorized()
}
@@ -83,10 +83,10 @@ trait AdminAuthenticator { self: ControllerBase =>
* Allows only guests and signed in users who can access the repository.
*/
trait ReferrerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
- protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
+ protected def referrersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
- private def authenticate(action: (RepositoryInfo) => Any) = {
+ private def authenticate(action: RepositoryInfo => Any) = {
val userName = params("owner")
val repoName = params("repository")
getRepository(userName, repoName).map { repository =>
@@ -103,12 +103,12 @@ trait ReferrerAuthenticator { self: ControllerBase & RepositoryService & Account
* Allows only signed in users who have read permission for the repository.
*/
trait ReadableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
- protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
+ protected def readableUsersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => {
authenticate(action(form, _))
}
- private def authenticate(action: (RepositoryInfo) => Any) = {
+ private def authenticate(action: RepositoryInfo => Any) = {
val userName = params("owner")
val repoName = params("repository")
getRepository(userName, repoName).map { repository =>
@@ -125,24 +125,19 @@ trait ReadableUsersAuthenticator { self: ControllerBase & RepositoryService & Ac
* Allows only signed in users who have write permission for the repository.
*/
trait WritableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
- protected def writableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
+ protected def writableUsersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
protected def writableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => {
authenticate(action(form, _))
}
- private def authenticate(action: (RepositoryInfo) => Any) = {
+ private def authenticate(action: RepositoryInfo => Any) = {
val userName = params("owner")
val repoName = params("repository")
getRepository(userName, repoName).map { repository =>
- context.loginAccount match {
- case Some(x) if (x.isAdmin) => action(repository)
- case Some(x) if (userName == x.userName) => action(repository)
- case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
- case Some(x)
- if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN, Role.DEVELOPER))
- .contains(x.userName)) =>
- action(repository)
- case _ => Unauthorized()
+ if (isWritable(repository.repository, context.loginAccount)) {
+ action(repository)
+ } else {
+ Unauthorized()
}
} getOrElse NotFound()
}
@@ -159,9 +154,9 @@ trait GroupManagerAuthenticator { self: ControllerBase & AccountService =>
context.loginAccount match {
case Some(x) if x.isAdmin => action
case Some(x) if x.userName == request.paths(0) => action
- case Some(x) if (getGroupMembers(request.paths(0)).exists { member =>
+ case Some(x) if getGroupMembers(request.paths(0)).exists { member =>
member.userName == x.userName && member.isManager
- }) =>
+ } =>
action
case _ => Unauthorized()
}
diff --git a/src/main/twirl/gitbucket/core/settings/branchprotection.scala.html b/src/main/twirl/gitbucket/core/settings/branchprotection.scala.html
index 5bd54131d..dc3940817 100644
--- a/src/main/twirl/gitbucket/core/settings/branchprotection.scala.html
+++ b/src/main/twirl/gitbucket/core/settings/branchprotection.scala.html
@@ -1,6 +1,6 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
branch: String,
- protection: gitbucket.core.api.ApiBranchProtection,
+ protection: gitbucket.core.api.ApiBranchProtectionResponse,
knownContexts: Seq[String],
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@@ -22,9 +22,44 @@
Disables force-pushes to this branch and prevents it from being deleted.
+
+
+
+
+
+
+
+
+
+
+
Restrict users who can push to this branch
+
+
+ @gitbucket.core.helper.html.account("userName-restrictions-user", 200, true, false)
+
+
+
+
+
+
+
+
+
+
+
+
When enabled, commits must first be pushed to another branch, then merged or pushed directly to @branch after status checks have passed.
@@ -48,14 +83,6 @@
}
-
-
-
-
Enforce required status checks for repository administrators.
-
}
@@ -68,59 +95,114 @@
}
diff --git a/src/test/scala/gitbucket/core/api/ApiSpecModels.scala b/src/test/scala/gitbucket/core/api/ApiSpecModels.scala
index 1adb50777..f2d466755 100644
--- a/src/test/scala/gitbucket/core/api/ApiSpecModels.scala
+++ b/src/test/scala/gitbucket/core/api/ApiSpecModels.scala
@@ -1,8 +1,9 @@
package gitbucket.core.api
-import java.util.{Calendar, Date, TimeZone}
+import gitbucket.core.api.ApiBranchProtectionResponse.Restrictions
-import gitbucket.core.model._
+import java.util.{Calendar, Date, TimeZone}
+import gitbucket.core.model.*
import gitbucket.core.plugin.PluginInfo
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchInfo
import gitbucket.core.service.RepositoryService.RepositoryInfo
@@ -15,7 +16,7 @@ object ApiSpecModels {
implicit val context: JsonFormat.Context = JsonFormat.Context("http://gitbucket.exmple.com", None)
- val date1 = {
+ val date1: Date = {
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
d.set(2011, 3, 14, 16, 0, 49)
d.getTime
@@ -29,7 +30,7 @@ object ApiSpecModels {
// Models
- val account = Account(
+ val account: Account = Account(
userName = "octocat",
fullName = "octocat",
mailAddress = "octocat@example.com",
@@ -45,10 +46,10 @@ object ApiSpecModels {
description = None
)
- val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
- val repo1Name = RepositoryName("octocat/Hello-World")
+ val sha1: String = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
+ val repo1Name: RepositoryName = RepositoryName("octocat/Hello-World")
- val repository = Repository(
+ val repository: Repository = Repository(
userName = repo1Name.owner,
repositoryName = repo1Name.name,
isPrivate = false,
@@ -73,7 +74,7 @@ object ApiSpecModels {
)
)
- val repositoryInfo = RepositoryInfo(
+ val repositoryInfo: RepositoryInfo = RepositoryInfo(
owner = repo1Name.owner,
name = repo1Name.name,
repository = repository,
@@ -101,7 +102,7 @@ object ApiSpecModels {
managers = Seq("myboss")
)
- val label = Label(
+ val label: Label = Label(
userName = repo1Name.owner,
repositoryName = repo1Name.name,
labelId = 10,
@@ -109,7 +110,7 @@ object ApiSpecModels {
color = "f29513"
)
- val issue = Issue(
+ val issue: Issue = Issue(
userName = repo1Name.owner,
repositoryName = repo1Name.name,
issueId = 1347,
@@ -124,14 +125,14 @@ object ApiSpecModels {
isPullRequest = false
)
- val issuePR = issue.copy(
+ val issuePR: Issue = issue.copy(
title = "new-feature",
content = Some("Please pull these awesome changes"),
closed = true,
isPullRequest = true
)
- val issueComment = IssueComment(
+ val issueComment: IssueComment = IssueComment(
userName = repo1Name.owner,
repositoryName = repo1Name.name,
issueId = issue.issueId,
@@ -143,7 +144,7 @@ object ApiSpecModels {
updatedDate = date1
)
- val pullRequest = PullRequest(
+ val pullRequest: PullRequest = PullRequest(
userName = repo1Name.owner,
repositoryName = repo1Name.name,
issueId = issuePR.issueId,
@@ -156,7 +157,7 @@ object ApiSpecModels {
isDraft = true
)
- val commitComment = CommitComment(
+ val commitComment: CommitComment = CommitComment(
userName = repo1Name.owner,
repositoryName = repo1Name.name,
commitId = sha1,
@@ -174,7 +175,7 @@ object ApiSpecModels {
originalNewLine = None
)
- val commitStatus = CommitStatus(
+ val commitStatus: CommitStatus = CommitStatus(
commitStatusId = 1,
userName = repo1Name.owner,
repositoryName = repo1Name.name,
@@ -188,7 +189,7 @@ object ApiSpecModels {
updatedDate = date1
)
- val milestone = Milestone(
+ val milestone: Milestone = Milestone(
userName = repo1Name.owner,
repositoryName = repo1Name.name,
milestoneId = 1,
@@ -200,28 +201,28 @@ object ApiSpecModels {
// APIs
- val apiUser = ApiUser(account)
+ val apiUser: ApiUser = ApiUser(account)
- val apiRepository = ApiRepository(
+ val apiRepository: ApiRepository = ApiRepository(
repository = repository,
owner = apiUser,
forkedCount = repositoryInfo.forkedCount,
watchers = 0
)
- val apiLabel = ApiLabel(
+ val apiLabel: ApiLabel = ApiLabel(
label = label,
repositoryName = repo1Name
)
- val apiMilestone = ApiMilestone(
+ val apiMilestone: ApiMilestone = ApiMilestone(
repository = repository,
milestone = milestone,
open_issue_count = 1,
closed_issue_count = 1
)
- val apiIssue = ApiIssue(
+ val apiIssue: ApiIssue = ApiIssue(
issue = issue,
repositoryName = repo1Name,
user = apiUser,
@@ -230,7 +231,7 @@ object ApiSpecModels {
milestone = Some(apiMilestone)
)
- val apiNotAssignedIssue = ApiIssue(
+ val apiNotAssignedIssue: ApiIssue = ApiIssue(
issue = issue,
repositoryName = repo1Name,
user = apiUser,
@@ -239,7 +240,7 @@ object ApiSpecModels {
milestone = Some(apiMilestone)
)
- val apiIssuePR = ApiIssue(
+ val apiIssuePR: ApiIssue = ApiIssue(
issue = issuePR,
repositoryName = repo1Name,
user = apiUser,
@@ -248,7 +249,7 @@ object ApiSpecModels {
milestone = Some(apiMilestone)
)
- val apiComment = ApiComment(
+ val apiComment: ApiComment = ApiComment(
comment = issueComment,
repositoryName = repo1Name,
issueId = issueComment.issueId,
@@ -256,7 +257,7 @@ object ApiSpecModels {
isPullRequest = false
)
- val apiCommentPR = ApiComment(
+ val apiCommentPR: ApiComment = ApiComment(
comment = issueComment,
repositoryName = repo1Name,
issueId = issueComment.issueId,
@@ -264,7 +265,7 @@ object ApiSpecModels {
isPullRequest = true
)
- val apiPullRequest = ApiPullRequest(
+ val apiPullRequest: ApiPullRequest = ApiPullRequest(
issue = issuePR,
pullRequest = pullRequest,
headRepo = apiRepository,
@@ -275,15 +276,14 @@ object ApiSpecModels {
mergedComment = Some((issueComment, account))
)
- // https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
- val apiPullRequestReviewComment = ApiPullRequestReviewComment(
+ val apiPullRequestReviewComment: ApiPullRequestReviewComment = ApiPullRequestReviewComment(
comment = commitComment,
commentedUser = apiUser,
repositoryName = repo1Name,
issueId = commitComment.issueId.get
)
- val commitInfo = (id: String) =>
+ val commitInfo: String => CommitInfo = (id: String) =>
CommitInfo(
id = id,
shortMessage = "short message",
@@ -299,12 +299,12 @@ object ApiSpecModels {
None
)
- val apiCommitListItem = ApiCommitListItem(
+ val apiCommitListItem: ApiCommitListItem = ApiCommitListItem(
commit = commitInfo(sha1),
repositoryName = repo1Name
)
- val apiCommit = {
+ val apiCommit: ApiCommit = {
val commit = commitInfo(sha1)
ApiCommit(
id = commit.id,
@@ -318,7 +318,7 @@ object ApiSpecModels {
)(repo1Name)
}
- val apiCommits = ApiCommits(
+ val apiCommits: ApiCommits = ApiCommits(
repositoryName = repo1Name,
commitInfo = commitInfo(sha1),
diffs = Seq(
@@ -348,42 +348,45 @@ object ApiSpecModels {
commentCount = 2
)
- val apiCommitStatus = ApiCommitStatus(
+ val apiCommitStatus: ApiCommitStatus = ApiCommitStatus(
status = commitStatus,
creator = apiUser
)
- val apiCombinedCommitStatus = ApiCombinedCommitStatus(
+ val apiCombinedCommitStatus: ApiCombinedCommitStatus = ApiCombinedCommitStatus(
sha = sha1,
statuses = Iterable((commitStatus, account)),
repository = apiRepository
)
- val apiBranchProtectionOutput = ApiBranchProtection(
+ val apiBranchProtectionOutput: ApiBranchProtectionResponse = ApiBranchProtectionResponse(
info = ProtectedBranchInfo(
owner = repo1Name.owner,
repository = repo1Name.name,
branch = "main",
enabled = true,
- contexts = Seq("continuous-integration/travis-ci"),
- includeAdministrators = true
+ contexts = Some(Seq("continuous-integration/travis-ci")),
+ enforceAdmins = true,
+ restrictionsUsers = Some(Seq("admin"))
)
)
- val apiBranchProtectionInput = new ApiBranchProtection(
+ val apiBranchProtectionInput: ApiBranchProtectionResponse = new ApiBranchProtectionResponse(
url = None,
enabled = true,
required_status_checks = Some(
- ApiBranchProtection.Status(
+ ApiBranchProtectionResponse.Status(
url = None,
- enforcement_level = ApiBranchProtection.Everyone,
+ enforcement_level = ApiBranchProtectionResponse.Everyone,
contexts = Seq("continuous-integration/travis-ci"),
contexts_url = None
)
- )
+ ),
+ restrictions = Some(Restrictions(users = Seq("admin"))),
+ enforce_admins = None
)
- val apiBranch = ApiBranch(
+ val apiBranch: ApiBranch = ApiBranch(
name = "main",
commit = ApiBranchCommit(sha1),
protection = apiBranchProtectionOutput
@@ -391,12 +394,12 @@ object ApiSpecModels {
repositoryName = repo1Name
)
- val apiBranchForList = ApiBranchForList(
+ val apiBranchForList: ApiBranchForList = ApiBranchForList(
name = "main",
commit = ApiBranchCommit(sha1)
)
- val apiContents = ApiContents(
+ val apiContents: ApiContents = ApiContents(
fileInfo = FileInfo(
id = ObjectId.fromString(sha1),
isDirectory = false,
@@ -413,14 +416,14 @@ object ApiSpecModels {
content = Some("README".getBytes("UTF-8"))
)
- val apiEndPoint = ApiEndPoint()
+ val apiEndPoint: ApiEndPoint = ApiEndPoint()
- val apiError = ApiError(
+ val apiError: ApiError = ApiError(
message = "A repository with this name already exists on this account",
documentation_url = Some("https://developer.github.com/v3/repos/#create")
)
- val apiGroup = ApiGroup(
+ val apiGroup: ApiGroup = ApiGroup(
account.copy(
isAdmin = true,
isGroupAccount = true,
@@ -428,7 +431,7 @@ object ApiSpecModels {
)
)
- val apiPlugin = ApiPlugin(
+ val apiPlugin: ApiPlugin = ApiPlugin(
plugin = PluginInfo(
pluginId = "gist",
pluginName = "Gist Plugin",
@@ -441,12 +444,12 @@ object ApiSpecModels {
)
)
- val apiPusher = ApiPusher(account)
+ val apiPusher: ApiPusher = ApiPusher(account)
// have both urls as https, as the expected samples are using https
- val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
+ val gitHubContext: JsonFormat.Context = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
- val apiRefHeadsMaster = ApiRef(
+ val apiRefHeadsMaster: ApiRef = ApiRef(
ref = "refs/heads/main",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/main"),
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
@@ -457,7 +460,7 @@ object ApiSpecModels {
)
)
- val apiRefTag = ApiRef(
+ val apiRefTag: ApiRef = ApiRef(
ref = "refs/tags/1.0",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/tags/1.0"),
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
@@ -468,9 +471,9 @@ object ApiSpecModels {
)
)
- val assetFileName = "010203040a0b0c0d"
+ val assetFileName: String = "010203040a0b0c0d"
- val apiReleaseAsset = ApiReleaseAsset(
+ val apiReleaseAsset: ApiReleaseAsset = ApiReleaseAsset(
name = "release.zip",
size = 100
)(
@@ -479,7 +482,7 @@ object ApiSpecModels {
repositoryName = repo1Name
)
- val apiRelease = ApiRelease(
+ val apiRelease: ApiRelease = ApiRelease(
name = "release1",
tag_name = "tag1",
body = Some("content"),
@@ -489,7 +492,7 @@ object ApiSpecModels {
// JSON String for APIs
- val jsonUser = """{
+ val jsonUser: String = """{
|"login":"octocat",
|"email":"octocat@example.com",
|"type":"User",
@@ -501,7 +504,7 @@ object ApiSpecModels {
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|}""".stripMargin
- val jsonRepository = s"""{
+ val jsonRepository: String = s"""{
|"name":"Hello-World",
|"full_name":"octocat/Hello-World",
|"description":"This your first repo!",
@@ -519,10 +522,10 @@ object ApiSpecModels {
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World"
|}""".stripMargin
- val jsonLabel =
+ val jsonLabel: String =
"""{"name":"bug","color":"f29513","url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/labels/bug"}"""
- val jsonMilestone = """{
+ val jsonMilestone: String = """{
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/milestones/1",
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/milestone/1",
|"id":1,
@@ -535,7 +538,7 @@ object ApiSpecModels {
|"due_on":"2011-04-14T16:00:49Z"
|}""".stripMargin
- val jsonIssue = s"""{
+ val jsonIssue: String = s"""{
|"number":1347,
|"title":"Found a bug",
|"user":$jsonUser,
@@ -552,7 +555,7 @@ object ApiSpecModels {
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347"
|}""".stripMargin
- val jsonNotAssignedIssue = s"""{
+ val jsonNotAssignedIssue: String = s"""{
|"number":1347,
|"title":"Found a bug",
|"user":$jsonUser,
@@ -568,7 +571,7 @@ object ApiSpecModels {
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347"
|}""".stripMargin
- val jsonIssuePR = s"""{
+ val jsonIssuePR: String = s"""{
|"number":1347,
|"title":"new-feature",
|"user":$jsonUser,
@@ -588,7 +591,7 @@ object ApiSpecModels {
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347"}
|}""".stripMargin
- val jsonPullRequest = s"""{
+ val jsonPullRequest: String = s"""{
|"number":1347,
|"state":"closed",
|"updated_at":"2011-04-14T16:00:49Z",
@@ -615,7 +618,7 @@ object ApiSpecModels {
|"statuses_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|}""".stripMargin
- val jsonPullRequestReviewComment = s"""{
+ val jsonPullRequestReviewComment: String = s"""{
|"id":29724692,
|"path":"README.md",
|"commit_id":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
@@ -632,7 +635,7 @@ object ApiSpecModels {
|"pull_request":{"href":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347"}}
|}""".stripMargin
- val jsonComment = s"""{
+ val jsonComment: String = s"""{
|"id":1,
|"user":$jsonUser,
|"body":"Me too",
@@ -641,7 +644,7 @@ object ApiSpecModels {
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347#comment-1"
|}""".stripMargin
- val jsonCommentPR = s"""{
+ val jsonCommentPR: String = s"""{
|"id":1,
|"user":$jsonUser,
|"body":"Me too",
@@ -650,7 +653,7 @@ object ApiSpecModels {
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347#comment-1"
|}""".stripMargin
- val jsonCommitListItem = s"""{
+ val jsonCommitListItem: String = s"""{
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"commit":{
|"message":"full message",
@@ -664,7 +667,7 @@ object ApiSpecModels {
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|}""".stripMargin
- val jsonCommit = (id: String) => s"""{
+ val jsonCommit: String => String = (id: String) => s"""{
|"id":"$id",
|"message":"full message",
|"timestamp":"2011-04-14T16:00:49Z",
@@ -677,7 +680,7 @@ object ApiSpecModels {
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/commit/$id"
|}""".stripMargin
- val jsonCommits = s"""{
+ val jsonCommits: String = s"""{
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e",
@@ -707,7 +710,7 @@ object ApiSpecModels {
|"patch":"@@ -1 +1,2 @@\\n-body1\\n\\\\ No newline at end of file\\n+body1\\n+body2\\n\\\\ No newline at end of file"}]
|}""".stripMargin
- val jsonCommitStatus = s"""{
+ val jsonCommitStatus: String = s"""{
|"created_at":"2011-04-14T16:00:49Z",
|"updated_at":"2011-04-14T16:00:49Z",
|"state":"success",
@@ -719,7 +722,7 @@ object ApiSpecModels {
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/statuses"
|}""".stripMargin
- val jsonCombinedCommitStatus = s"""{
+ val jsonCombinedCommitStatus: String = s"""{
|"state":"success",
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"total_count":1,
@@ -728,7 +731,7 @@ object ApiSpecModels {
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/status"
|}""".stripMargin
- val jsonBranchProtectionOutput =
+ val jsonBranchProtectionOutput: String =
"""{
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection",
|"enabled":true,
@@ -736,15 +739,25 @@ object ApiSpecModels {
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks",
|"enforcement_level":"everyone",
|"contexts":["continuous-integration/travis-ci"],
- |"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"}
+ |"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"
+ |},
+ |"restrictions":{
+ |"users":["admin"]
+ |},
+ |"enforce_admins":{
+ |"enabled":true
+ |}
|}""".stripMargin
- val jsonBranchProtectionInput =
+ val jsonBranchProtectionInput: String =
"""{
|"enabled":true,
|"required_status_checks":{
|"enforcement_level":"everyone",
|"contexts":["continuous-integration/travis-ci"]
+ |},
+ |"restrictions":{
+ |"users":["admin"]
|}
|}""".stripMargin
@@ -757,9 +770,9 @@ object ApiSpecModels {
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/main"}
|}""".stripMargin
- val jsonBranchForList = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
+ val jsonBranchForList: String = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
- val jsonContents =
+ val jsonContents: String =
"""{
|"type":"file",
|"name":"README.md",
@@ -770,14 +783,14 @@ object ApiSpecModels {
|"download_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/doc/README.md"
|}""".stripMargin
- val jsonEndPoint = """{"rate_limit_url":"http://gitbucket.exmple.com/api/v3/rate_limit"}"""
+ val jsonEndPoint: String = """{"rate_limit_url":"http://gitbucket.exmple.com/api/v3/rate_limit"}"""
- val jsonError = """{
+ val jsonError: String = """{
|"message":"A repository with this name already exists on this account",
|"documentation_url":"https://developer.github.com/v3/repos/#create"
|}""".stripMargin
- val jsonGroup = """{
+ val jsonGroup: String = """{
|"login":"octocat",
|"description":"Admin group",
|"created_at":"2011-04-14T16:00:49Z",
@@ -787,7 +800,7 @@ object ApiSpecModels {
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|}""".stripMargin
- val jsonPlugin = """{
+ val jsonPlugin: String = """{
|"id":"gist",
|"name":"Gist Plugin",
|"version":"4.16.0",
@@ -795,12 +808,12 @@ object ApiSpecModels {
|"jarFileName":"gitbucket-gist-plugin-gitbucket_4.30.0-SNAPSHOT-4.17.0.jar"
|}""".stripMargin
- val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}"""
+ val jsonPusher: String = """{"name":"octocat","email":"octocat@example.com"}"""
// I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag"
- val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
+ val jsonRef: String = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
- val jsonRefHeadsMain =
+ val jsonRefHeadsMain: String =
"""{
|"ref": "refs/heads/main",
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
@@ -812,7 +825,7 @@ object ApiSpecModels {
|}
|}""".stripMargin
- val jsonRefTag =
+ val jsonRefTag: String =
"""{
|"ref": "refs/tags/1.0",
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
@@ -824,7 +837,7 @@ object ApiSpecModels {
|}
|}""".stripMargin
- val jsonReleaseAsset =
+ val jsonReleaseAsset: String =
s"""{
|"name":"release.zip",
|"size":100,
@@ -833,7 +846,7 @@ object ApiSpecModels {
|"browser_download_url":"http://gitbucket.exmple.com/octocat/Hello-World/releases/tag1/assets/${assetFileName}"
|}""".stripMargin
- val jsonRelease =
+ val jsonRelease: String =
s"""{
|"name":"release1",
|"tag_name":"tag1",
diff --git a/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala b/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala
index a8f2c53e2..9c958b832 100644
--- a/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala
+++ b/src/test/scala/gitbucket/core/api/JsonFormatSpec.scala
@@ -1,19 +1,19 @@
package gitbucket.core.api
-import org.json4s.Formats
+import org.json4s.{Formats, JValue, jvalue2extractable}
import org.json4s.jackson.JsonMethods
-import org.json4s.jvalue2extractable
+import org.scalatest.Assertion
import org.scalatest.funsuite.AnyFunSuite
class JsonFormatSpec extends AnyFunSuite {
- import ApiSpecModels._
+ import ApiSpecModels.*
implicit val format: Formats = JsonFormat.jsonFormats
private def expected(json: String) = json.replaceAll("\n", "")
- def normalizeJson(json: String) = {
+ def normalizeJson(json: String): JValue = {
org.json4s.jackson.parseJson(json)
}
- def assertEqualJson(actual: String, expected: String) = {
+ def assertEqualJson(actual: String, expected: String): Assertion = {
assert(normalizeJson(actual) == normalizeJson(expected))
}
@@ -57,7 +57,9 @@ class JsonFormatSpec extends AnyFunSuite {
assert(JsonFormat(apiBranchProtectionOutput) == expected(jsonBranchProtectionOutput))
}
test("deserialize apiBranchProtection") {
- assert(JsonMethods.parse(jsonBranchProtectionInput).extract[ApiBranchProtection] == apiBranchProtectionInput)
+ assert(
+ JsonMethods.parse(jsonBranchProtectionInput).extract[ApiBranchProtectionResponse] == apiBranchProtectionInput
+ )
}
test("apiBranch") {
assert(JsonFormat(apiBranch) == expected(jsonBranch))
diff --git a/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala b/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala
index 80a1c5691..be56f2a87 100644
--- a/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala
+++ b/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala
@@ -29,26 +29,28 @@ class ProtectedBranchServiceSpec
it("should enable and update and disable") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
- enableBranchProtection("user1", "repo1", "branch", false, Nil)
+ enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
assert(
getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo(
"user1",
"repo1",
"branch",
true,
- Nil,
- false
+ None,
+ false,
+ None
)
)
- enableBranchProtection("user1", "repo1", "branch", true, Seq("hoge", "huge"))
+ enableBranchProtection("user1", "repo1", "branch", true, true, Seq("hoge", "huge"), false, Nil)
assert(
getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo(
"user1",
"repo1",
"branch",
- true,
- Seq("hoge", "huge"),
- true
+ enabled = true,
+ contexts = Some(Seq("hoge", "huge")),
+ enforceAdmins = true,
+ restrictionsUsers = None
)
)
disableBranchProtection("user1", "repo1", "branch")
@@ -57,21 +59,21 @@ class ProtectedBranchServiceSpec
)
}
}
- it("should empty contexts is no-include-administrators") {
+ it("should empty contexts is include-administrators") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
- enableBranchProtection("user1", "repo1", "branch", false, Nil)
- assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
- enableBranchProtection("user1", "repo1", "branch", true, Nil)
- assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
+ enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
+ assert(!getProtectedBranchInfo("user1", "repo1", "branch").enforceAdmins)
+ enableBranchProtection("user1", "repo1", "branch", true, false, Nil, false, Nil)
+ assert(getProtectedBranchInfo("user1", "repo1", "branch").enforceAdmins)
}
}
it("getProtectedBranchList") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
- enableBranchProtection("user1", "repo1", "branch", false, Nil)
- enableBranchProtection("user1", "repo1", "branch2", false, Seq("fuga"))
- enableBranchProtection("user1", "repo1", "branch3", true, Seq("hoge"))
+ enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
+ enableBranchProtection("user1", "repo1", "branch2", false, false, Seq("fuga"), false, Nil)
+ enableBranchProtection("user1", "repo1", "branch3", true, false, Seq("hoge"), false, Nil)
assert(getProtectedBranchList("user1", "repo1").toSet == Set("branch", "branch2", "branch3"))
}
}
@@ -87,12 +89,12 @@ class ProtectedBranchServiceSpec
ReceiveCommand.Type.UPDATE_NONFASTFORWARD
)
generateNewUserWithDBRepository("user1", "repo1")
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
- enableBranchProtection("user1", "repo1", "branch", false, Nil)
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
+ enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
- "Cannot force-push to a protected branch"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user1", false)
+ .contains("Cannot force-push to a protected branch")
)
}
}
@@ -109,12 +111,12 @@ class ProtectedBranchServiceSpec
ReceiveCommand.Type.UPDATE_NONFASTFORWARD
)
generateNewUserWithDBRepository("user1", "repo1")
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
- enableBranchProtection("user1", "repo1", "branch", false, Nil)
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
+ enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
- "Cannot force-push to a protected branch"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user2", false)
+ .contains("Cannot force-push to a protected branch")
)
}
}
@@ -131,33 +133,33 @@ class ProtectedBranchServiceSpec
ReceiveCommand.Type.UPDATE
)
val user1 = generateNewUserWithDBRepository("user1", "repo1")
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
- enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
+ enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must"), false, Nil)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
- "Required status check \"must\" is expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user2", false)
+ .contains("Required status check \"must\" is expected")
)
- enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
+ enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must", "must2"), false, Nil)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
- "2 of 2 required status checks are expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user2", false)
+ .contains("2 of 2 required status checks are expected")
)
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
- "2 of 2 required status checks are expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user2", false)
+ .contains("2 of 2 required status checks are expected")
)
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
- "Required status check \"must2\" is expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user2", false)
+ .contains("Required status check \"must2\" is expected")
)
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
}
}
}
@@ -173,37 +175,60 @@ class ProtectedBranchServiceSpec
ReceiveCommand.Type.UPDATE
)
val user1 = generateNewUserWithDBRepository("user1", "repo1")
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
- enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
- enableBranchProtection("user1", "repo1", "branch", true, Seq("must"))
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
+ enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must"), false, Nil)
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
+ enableBranchProtection("user1", "repo1", "branch", true, true, Seq("must"), false, Nil)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
- "Required status check \"must\" is expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user1", false)
+ .contains("Required status check \"must\" is expected")
)
- enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
- enableBranchProtection("user1", "repo1", "branch", true, Seq("must", "must2"))
+ enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must", "must2"), false, Nil)
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
+ enableBranchProtection("user1", "repo1", "branch", true, true, Seq("must", "must2"), false, Nil)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
- "2 of 2 required status checks are expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user1", false)
+ .contains("2 of 2 required status checks are expected")
)
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
- "2 of 2 required status checks are expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user1", false)
+ .contains("2 of 2 required status checks are expected")
)
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
assert(
- receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
- "Required status check \"must2\" is expected"
- )
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user1", false)
+ .contains("Required status check \"must2\" is expected")
)
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
- assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
+ }
+ }
+ }
+ it("should restrict push to allowed users only") {
+ withTestDB { implicit session =>
+ withTestRepository { git =>
+ val rp = new ReceivePack(git.getRepository)
+ rp.setAllowNonFastForwards(true)
+ val rc = new ReceiveCommand(
+ ObjectId.fromString(sha),
+ ObjectId.fromString(sha2),
+ "refs/heads/branch",
+ ReceiveCommand.Type.UPDATE
+ )
+ generateNewUserWithDBRepository("user1", "repo1")
+ generateNewAccount("user2")
+ enableBranchProtection("user1", "repo1", "branch", false, false, Nil, true, Seq("user2"))
+ assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
+ assert(
+ receiveHook
+ .preReceive("user1", "repo1", rp, rc, "user3", false)
+ .contains("You do not have permission to push to this branch")
+ )
}
}
}
@@ -212,29 +237,53 @@ class ProtectedBranchServiceSpec
it("administrator is owner") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
- val x = ProtectedBranchInfo("user1", "repo1", "branch", true, Nil, false)
- assert(x.isAdministrator("user1") == true)
- assert(x.isAdministrator("user2") == false)
+ val x = ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(Nil),
+ enforceAdmins = false,
+ restrictionsUsers = None
+ )
+ assert(x.isAdministrator("user1"))
+ assert(!x.isAdministrator("user2"))
}
}
it("administrator is manager") {
withTestDB { implicit session =>
- val x = ProtectedBranchInfo("grp1", "repo1", "branch", true, Nil, false)
+ val x = ProtectedBranchInfo(
+ "grp1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(Nil),
+ enforceAdmins = false,
+ restrictionsUsers = None
+ )
x.createGroup("grp1", None, None)
generateNewAccount("user1")
generateNewAccount("user2")
generateNewAccount("user3")
x.updateGroupMembers("grp1", List("user1" -> true, "user2" -> false))
- assert(x.isAdministrator("user1") == true)
- assert(x.isAdministrator("user2") == false)
- assert(x.isAdministrator("user3") == false)
+ assert(x.isAdministrator("user1"))
+ assert(!x.isAdministrator("user2"))
+ assert(!x.isAdministrator("user3"))
}
}
it("unSuccessedContexts") {
withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1", "repo1")
- val x = ProtectedBranchInfo("user1", "repo1", "branch", true, List("must"), false)
+ val x = ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(List("must")),
+ enforceAdmins = false,
+ restrictionsUsers = None
+ )
assert(x.unSuccessedContexts(sha) == Set("must"))
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
assert(x.unSuccessedContexts(sha) == Set("must"))
@@ -251,7 +300,15 @@ class ProtectedBranchServiceSpec
it("unSuccessedContexts when empty") {
withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1", "repo1")
- val x = ProtectedBranchInfo("user1", "repo1", "branch", true, Nil, false)
+ val x = ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(Nil),
+ enforceAdmins = false,
+ restrictionsUsers = None
+ )
val sha = "0c77148632618b59b6f70004e3084002be2b8804"
assert(x.unSuccessedContexts(sha) == Set())
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
@@ -261,23 +318,63 @@ class ProtectedBranchServiceSpec
it("if disabled, needStatusCheck is false") {
withTestDB { implicit session =>
assert(
- ProtectedBranchInfo("user1", "repo1", "branch", false, Seq("must"), true).needStatusCheck("user1") == false
+ !ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = false,
+ contexts = Some(Seq("must")),
+ enforceAdmins = true,
+ restrictionsUsers = None
+ ).needStatusCheck("user1")
)
}
}
it("needStatusCheck includeAdministrators") {
withTestDB { implicit session =>
assert(
- ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), false).needStatusCheck("user2") == true
+ ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(Seq("must")),
+ enforceAdmins = false,
+ restrictionsUsers = None
+ ).needStatusCheck("user2")
)
assert(
- ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), false).needStatusCheck("user1") == false
+ !ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(Seq("must")),
+ enforceAdmins = false,
+ restrictionsUsers = None
+ ).needStatusCheck("user1")
)
assert(
- ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), true).needStatusCheck("user2") == true
+ ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(Seq("must")),
+ enforceAdmins = true,
+ restrictionsUsers = None
+ ).needStatusCheck("user2")
)
assert(
- ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), true).needStatusCheck("user1") == true
+ ProtectedBranchInfo(
+ "user1",
+ "repo1",
+ "branch",
+ enabled = true,
+ contexts = Some(Seq("must")),
+ enforceAdmins = true,
+ restrictionsUsers = None
+ ).needStatusCheck("user1")
)
}
}
diff --git a/src/test/scala/gitbucket/core/service/RepositoryServiceSpec.scala b/src/test/scala/gitbucket/core/service/RepositoryServiceSpec.scala
index 1b3b9df77..d7cd25c95 100644
--- a/src/test/scala/gitbucket/core/service/RepositoryServiceSpec.scala
+++ b/src/test/scala/gitbucket/core/service/RepositoryServiceSpec.scala
@@ -21,7 +21,7 @@ class RepositoryServiceSpec extends AnyFunSuite with ServiceSpecBase with Reposi
now = new java.util.Date
)
- service.enableBranchProtection("root", "repo", "branch", true, Seq("must1", "must2"))
+ service.enableBranchProtection("root", "repo", "branch", true, true, Seq("must1", "must2"), false, Nil)
val orgPbi = service.getProtectedBranchInfo("root", "repo", "branch")
val org = service.getCommitStatus("root", "repo", id).get