mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 06:15:27 +02:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2904bcf4a7 | ||
|
|
6630fa2f37 | ||
|
|
351e63e7b6 | ||
|
|
ea0f35a0a1 | ||
|
|
623c53e169 | ||
|
|
3e6fd2caf8 | ||
|
|
39f1aa4487 | ||
|
|
8ffd905a9f | ||
|
|
668f9ef919 | ||
|
|
ffb9bb10f5 | ||
|
|
2618f54442 | ||
|
|
6b3218dd43 | ||
|
|
56a9b7b0f1 | ||
|
|
4f4afc5686 | ||
|
|
87fb136b85 | ||
|
|
7af271e14a | ||
|
|
f44d44cb4a | ||
|
|
e7fc5f1753 | ||
|
|
f0e2775861 | ||
|
|
2488ab9bd4 | ||
|
|
f0872d410c | ||
|
|
9d69cc9d45 | ||
|
|
1c66052372 | ||
|
|
158f799ca1 | ||
|
|
907532fd13 | ||
|
|
0f6a433623 | ||
|
|
00eab5d584 | ||
|
|
5d928b1a62 | ||
|
|
50d6f0c96f | ||
|
|
a60b43b862 | ||
|
|
4b1235b484 | ||
|
|
f354b9cfd7 | ||
|
|
0c2283ce28 | ||
|
|
840479a022 | ||
|
|
1bceaaab1d | ||
|
|
65ece3292a | ||
|
|
e410623cac | ||
|
|
09f7f036aa | ||
|
|
5249224dec | ||
|
|
00def3a46d | ||
|
|
134c0010b5 | ||
|
|
fe3b40557a | ||
|
|
d3cdc5d5fc | ||
|
|
7ebb28be74 | ||
|
|
707cd3c5c3 | ||
|
|
4c5017d108 | ||
|
|
fdd91b1e0e | ||
|
|
9124777ce7 | ||
|
|
7c575fdc52 | ||
|
|
f9d6f1334f | ||
|
|
65d4900325 | ||
|
|
64248d1fce | ||
|
|
bec75120bc | ||
|
|
3b0eed48d9 | ||
|
|
25c4b1e6a7 | ||
|
|
cccff46715 | ||
|
|
fc0ffd1b4f | ||
|
|
b70a2a2327 | ||
|
|
b5ca7ca0e1 | ||
|
|
7bb5379b45 | ||
|
|
5692a8c83e | ||
|
|
6c6126148e | ||
|
|
5b2e24daef | ||
|
|
29f390e48c | ||
|
|
c49eff6e54 | ||
|
|
27930a5849 | ||
|
|
6ffc139d2f | ||
|
|
59ed027b60 | ||
|
|
5dc55822d7 | ||
|
|
9bfe5115cc | ||
|
|
aaf9c65f30 | ||
|
|
d6197261fb | ||
|
|
8fd7df2a9d | ||
|
|
4eb148f4a6 | ||
|
|
8f1e460893 | ||
|
|
8c80f8a506 | ||
|
|
9eb9fc666c | ||
|
|
d70c6cece7 | ||
|
|
dbdee135a3 | ||
|
|
132bb6bee4 | ||
|
|
2dfa7a1190 | ||
|
|
06d559b47e | ||
|
|
83baaa6ed9 | ||
|
|
85d38a47f1 | ||
|
|
0c3c6ea15a | ||
|
|
2ce436bddc | ||
|
|
a60c607fcb | ||
|
|
0456739118 | ||
|
|
368052bd8f | ||
|
|
ce916a7d4b | ||
|
|
60ff046823 |
18
README.md
18
README.md
@@ -65,6 +65,24 @@ Support
|
||||
|
||||
Release Notes
|
||||
-------------
|
||||
## 4.8 - 23 Dec 2016
|
||||
- Search for repository names from the global header
|
||||
- Filter repositories on the sidebar of the dashboard
|
||||
- Search issues and wiki
|
||||
- Keep pull request comments after new commits are pushed
|
||||
- New web API to get a single issue
|
||||
- Performance improvement for the repository viewer
|
||||
|
||||
### 4.7.1 - 28 Nov 2016
|
||||
- Bug fix: group repositories are not shown in the your repositories list on the sidebar
|
||||
- Small performance improvement of the dashboard
|
||||
|
||||
### 4.7 - 26 Nov 2016
|
||||
- New permission system
|
||||
- Dropdown filter for issue labels, milestones and assignees
|
||||
- Keep sidebar folding status
|
||||
- Link from milestone label to the issue list
|
||||
|
||||
### 4.6 - 29 Oct 2016
|
||||
- Add disable option for forking
|
||||
- Add History button to wiki page
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.6.0"
|
||||
val GitBucketVersion = "4.8"
|
||||
val ScalatraVersion = "2.4.1"
|
||||
val JettyVersion = "9.3.9.v20160517"
|
||||
|
||||
@@ -15,6 +15,7 @@ scalaVersion := "2.11.8"
|
||||
// dependency settings
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
Resolver.jcenterRepo,
|
||||
"amateras" at "http://amateras.sourceforge.jp/mvn/",
|
||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
|
||||
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||
@@ -45,6 +46,8 @@ libraryDependencies ++= Seq(
|
||||
"com.typesafe" % "config" % "1.3.0",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.3.15",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
||||
"org.cache2k" % "cache2k-all" % "1.0.0.CR1",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
|
||||
2
src/main/resources/update/gitbucket-core_4.7.sql
Normal file
2
src/main/resources/update/gitbucket-core_4.7.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- DELETE COLLABORATORS IN GROUP REPOSITORIES
|
||||
DELETE FROM COLLABORATOR WHERE USER_NAME IN (SELECT USER_NAME FROM ACCOUNT WHERE GROUP_ACCOUNT = TRUE)
|
||||
33
src/main/resources/update/gitbucket-core_4.7.xml
Normal file
33
src/main/resources/update/gitbucket-core_4.7.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<addColumn tableName="COLLABORATOR">
|
||||
<column name="ROLE" type="varchar(10)" nullable="false" defaultValue="ADMIN"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="REPOSITORY">
|
||||
<column name="WIKI_OPTION" type="varchar(10)" nullable="false" defaultValue="DISABLE"/>
|
||||
<column name="ISSUES_OPTION" type="varchar(10)" nullable="false" defaultValue="DISABLE"/>
|
||||
</addColumn>
|
||||
<update tableName="REPOSITORY">
|
||||
<column name="WIKI_OPTION" value="DISABLE"/>
|
||||
<where>ENABLE_WIKI = FALSE</where>
|
||||
</update>
|
||||
<update tableName="REPOSITORY">
|
||||
<column name="WIKI_OPTION" value="PRIVATE"/>
|
||||
<where>ENABLE_WIKI = TRUE AND ALLOW_WIKI_EDITING = FALSE</where>
|
||||
</update>
|
||||
<update tableName="REPOSITORY">
|
||||
<column name="WIKI_OPTION" value="PUBLIC"/>
|
||||
<where>ENABLE_WIKI = TRUE AND ALLOW_WIKI_EDITING = TRUE</where>
|
||||
</update>
|
||||
<update tableName="REPOSITORY">
|
||||
<column name="ISSUES_OPTION" value="DISABLE"/>
|
||||
<where>ENABLE_ISSUES = FALSE</where>
|
||||
</update>
|
||||
<update tableName="REPOSITORY">
|
||||
<column name="ISSUES_OPTION" value="PUBLIC"/>
|
||||
<where>ENABLE_ISSUES = TRUE</where>
|
||||
</update>
|
||||
<dropColumn tableName="REPOSITORY" columnName="ENABLE_WIKI"/>
|
||||
<dropColumn tableName="REPOSITORY" columnName="ALLOW_WIKI_EDITING"/>
|
||||
<dropColumn tableName="REPOSITORY" columnName="ENABLE_ISSUES"/>
|
||||
</changeSet>
|
||||
@@ -18,5 +18,11 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
new Version("4.5.0"),
|
||||
new Version("4.6.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.6.xml")
|
||||
)
|
||||
),
|
||||
new Version("4.7.0",
|
||||
new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
|
||||
new SqlMigration("update/gitbucket-core_4.7.sql")
|
||||
),
|
||||
new Version("4.7.1"),
|
||||
new Version("4.8")
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.{Issue, PullRequest}
|
||||
|
||||
import gitbucket.core.model.{Account, Issue, IssueComment, PullRequest}
|
||||
import java.util.Date
|
||||
|
||||
|
||||
@@ -15,6 +14,9 @@ case class ApiPullRequest(
|
||||
head: ApiPullRequest.Commit,
|
||||
base: ApiPullRequest.Commit,
|
||||
mergeable: Option[Boolean],
|
||||
merged: Boolean,
|
||||
merged_at: Option[Date],
|
||||
merged_by: Option[ApiUser],
|
||||
title: String,
|
||||
body: String,
|
||||
user: ApiUser) {
|
||||
@@ -31,7 +33,14 @@ case class ApiPullRequest(
|
||||
}
|
||||
|
||||
object ApiPullRequest{
|
||||
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest =
|
||||
def apply(
|
||||
issue: Issue,
|
||||
pullRequest: PullRequest,
|
||||
headRepo: ApiRepository,
|
||||
baseRepo: ApiRepository,
|
||||
user: ApiUser,
|
||||
mergedComment: Option[(IssueComment, Account)]
|
||||
): ApiPullRequest =
|
||||
ApiPullRequest(
|
||||
number = issue.issueId,
|
||||
updated_at = issue.updatedDate,
|
||||
@@ -45,6 +54,9 @@ object ApiPullRequest{
|
||||
ref = pullRequest.branch,
|
||||
repo = baseRepo)(issue.userName),
|
||||
mergeable = None, // TODO: need check mergeable.
|
||||
merged = mergedComment.isDefined,
|
||||
merged_at = mergedComment.map { case (comment, _) => comment.registeredDate },
|
||||
merged_by = mergedComment.map { case (_, account) => ApiUser(account) },
|
||||
title = issue.title,
|
||||
body = issue.content.getOrElse(""),
|
||||
user = user
|
||||
|
||||
@@ -30,7 +30,7 @@ object ApiUser{
|
||||
def apply(user: Account): ApiUser = ApiUser(
|
||||
login = user.userName,
|
||||
email = user.mailAddress,
|
||||
`type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
|
||||
`type` = if(user.isGroupAccount){ "Organization" } else { "User" },
|
||||
site_admin = user.isAdmin,
|
||||
created_at = user.registeredDate
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ case class CreateARepository(
|
||||
auto_init: Boolean = false
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
name.length<=40 &&
|
||||
name.length <= 100 &&
|
||||
name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
|
||||
!name.startsWith("_") &&
|
||||
!name.startsWith("-")
|
||||
|
||||
@@ -319,13 +319,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
// Update COLLABORATOR for group repositories
|
||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
removeCollaborators(form.groupName, repositoryName)
|
||||
members.foreach { case (userName, isManager) =>
|
||||
addCollaborator(form.groupName, repositoryName, userName)
|
||||
}
|
||||
}
|
||||
// // Update COLLABORATOR for group repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// removeCollaborators(form.groupName, repositoryName)
|
||||
// members.foreach { case (userName, isManager) =>
|
||||
// addCollaborator(form.groupName, repositoryName, userName)
|
||||
// }
|
||||
// }
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
redirect(s"/${form.groupName}")
|
||||
@@ -402,13 +402,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
parentUserName = Some(repository.owner)
|
||||
)
|
||||
|
||||
// Add collaborators for group repository
|
||||
val ownerAccount = getAccountByUserName(accountName).get
|
||||
if(ownerAccount.isGroupAccount){
|
||||
getGroupMembers(accountName).foreach { member =>
|
||||
addCollaborator(accountName, repository.name, member.userName)
|
||||
}
|
||||
}
|
||||
// // Add collaborators for group repository
|
||||
// val ownerAccount = getAccountByUserName(accountName).get
|
||||
// if(ownerAccount.isGroupAccount){
|
||||
// getGroupMembers(accountName).foreach { member =>
|
||||
// addCollaborator(accountName, repository.name, member.userName)
|
||||
// }
|
||||
// }
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(accountName, repository.name)
|
||||
|
||||
@@ -22,6 +22,7 @@ class ApiController extends ApiControllerBase
|
||||
with IssuesService
|
||||
with LabelsService
|
||||
with PullRequestService
|
||||
with CommitsService
|
||||
with CommitStatusService
|
||||
with RepositoryCreationService
|
||||
with HandleCommentService
|
||||
@@ -35,7 +36,7 @@ class ApiController extends ApiControllerBase
|
||||
with GroupManagerAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with CollaboratorsAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait ApiControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -52,7 +53,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
with GroupManagerAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with CollaboratorsAuthenticator =>
|
||||
with WritableUsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/#root-endpoint
|
||||
@@ -102,7 +103,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
).map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -132,9 +133,12 @@ trait ApiControllerBase extends ControllerBase {
|
||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||
val content = getContentFromId(git, f.id, largeFile)
|
||||
request.getHeader("Accept") match {
|
||||
case "application/vnd.github.v3.raw" =>
|
||||
case "application/vnd.github.v3.raw" => {
|
||||
contentType = "application/vnd.github.v3.raw"
|
||||
content
|
||||
case "application/vnd.github.v3.html" if isRenderable(f.name) =>
|
||||
}
|
||||
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
||||
contentType = "application/vnd.github.v3.html"
|
||||
content.map(c =>
|
||||
List(
|
||||
"<div data-path=\"", path, "\" id=\"file\">", "<article>",
|
||||
@@ -142,7 +146,9 @@ trait ApiControllerBase extends ControllerBase {
|
||||
"</article>", "</div>"
|
||||
).mkString
|
||||
)
|
||||
case "application/vnd.github.v3.html" =>
|
||||
}
|
||||
case "application/vnd.github.v3.html" => {
|
||||
contentType = "application/vnd.github.v3.html"
|
||||
content.map(c =>
|
||||
List(
|
||||
"<div data-path=\"", path, "\" id=\"file\">", "<div class=\"plain\">", "<pre>",
|
||||
@@ -150,6 +156,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
"</pre>", "</div>", "</div>"
|
||||
).mkString
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
Some(JsonFormat(ApiContents(f, content)))
|
||||
}
|
||||
@@ -177,7 +184,8 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository =>
|
||||
JsonFormat(getCollaborators(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
|
||||
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
||||
JsonFormat(getCollaboratorUserNames(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -253,7 +261,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
(for{
|
||||
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
|
||||
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
} yield {
|
||||
if(protection.enabled){
|
||||
@@ -275,13 +283,26 @@ trait ApiControllerBase extends ControllerBase {
|
||||
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/#get-a-single-issue
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
(for{
|
||||
issueId <- params("id").toIntOpt
|
||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||
openedUser <- getAccountByUserName(issue.openedUserName)
|
||||
} yield {
|
||||
JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(openedUser)))
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
||||
(for{
|
||||
issueId <- params("id").toIntOpt
|
||||
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
|
||||
issueId <- params("id").toIntOpt
|
||||
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
|
||||
} yield {
|
||||
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
||||
}) getOrElse NotFound()
|
||||
@@ -327,7 +348,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* Create a label
|
||||
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
|
||||
post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { repository =>
|
||||
(for{
|
||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||
} yield {
|
||||
@@ -352,7 +373,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* Update a label
|
||||
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
||||
(for{
|
||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||
} yield {
|
||||
@@ -362,12 +383,14 @@ trait ApiControllerBase extends ControllerBase {
|
||||
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||
JsonFormat(ApiLabel(
|
||||
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||
RepositoryName(repository)))
|
||||
RepositoryName(repository)
|
||||
))
|
||||
} else {
|
||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||
UnprocessableEntity(ApiError(
|
||||
"Validation Failed",
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||
))
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -378,7 +401,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
* Delete a label
|
||||
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||
deleteLabel(repository.owner, repository.name, label.labelId)
|
||||
@@ -406,11 +429,12 @@ trait ApiControllerBase extends ControllerBase {
|
||||
|
||||
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
|
||||
ApiPullRequest(
|
||||
issue,
|
||||
pullRequest,
|
||||
ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
ApiRepository(repository, ApiUser(baseOwner)),
|
||||
ApiUser(issueUser)
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||
user = ApiUser(issueUser),
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -420,20 +444,22 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
||||
(for{
|
||||
issueId <- params("id").toIntOpt
|
||||
issueId <- params("id").toIntOpt
|
||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set())
|
||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set.empty)
|
||||
baseOwner <- users.get(repository.owner)
|
||||
headOwner <- users.get(pullRequest.requestUserName)
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||
} yield {
|
||||
JsonFormat(ApiPullRequest(
|
||||
issue,
|
||||
pullRequest,
|
||||
ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
ApiRepository(repository, ApiUser(baseOwner)),
|
||||
ApiUser(issueUser)))
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||
user = ApiUser(issueUser),
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||
))
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -449,7 +475,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
||||
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
||||
val repoFullName = RepositoryName(repository)
|
||||
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
|
||||
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map { c => ApiCommitListItem(new CommitInfo(c), repoFullName) }.toList
|
||||
JsonFormat(commits)
|
||||
}
|
||||
}
|
||||
@@ -466,16 +492,16 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
|
||||
post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository =>
|
||||
(for{
|
||||
ref <- params.get("sha")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
||||
creator <- context.loginAccount
|
||||
state <- CommitState.valueOf(data.state)
|
||||
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
|
||||
state, data.target_url, data.description, new java.util.Date(), creator)
|
||||
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
||||
ref <- params.get("sha")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
||||
creator <- context.loginAccount
|
||||
state <- CommitState.valueOf(data.state)
|
||||
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
|
||||
state, data.target_url, data.description, new java.util.Date(), creator)
|
||||
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
||||
} yield {
|
||||
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
|
||||
}) getOrElse NotFound()
|
||||
@@ -513,9 +539,9 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
|
||||
(for{
|
||||
ref <- params.get("ref")
|
||||
ref <- params.get("ref")
|
||||
owner <- getAccountByUserName(repository.owner)
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
} yield {
|
||||
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
||||
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
||||
@@ -523,7 +549,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
|
||||
case agent if agent.contains("Win") => "windows"
|
||||
case _ => null
|
||||
}
|
||||
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
|
||||
|
||||
/**
|
||||
* Get object from cache.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.dashboard.html
|
||||
import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService}
|
||||
import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator}
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.{Keys, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.service.IssuesService._
|
||||
|
||||
class DashboardController extends DashboardControllerBase
|
||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
with IssuesService with PullRequestService with RepositoryService with AccountService with CommitsService
|
||||
with UsersAuthenticator
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
@@ -76,7 +76,7 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true),
|
||||
Nil,
|
||||
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true),
|
||||
Nil,
|
||||
getUserRepositories(userName, withoutPhysicalInfo = true))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import gitbucket.core.model.Account
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
|
||||
|
||||
import gitbucket.core.util.{Keys, LDAPUtil, ReferrerAuthenticator, StringUtil, UsersAuthenticator}
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import org.scalatra.Ok
|
||||
|
||||
|
||||
class IndexController extends IndexControllerBase
|
||||
@@ -36,23 +36,11 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
|
||||
get("/"){
|
||||
val loginAccount = context.loginAccount
|
||||
if(loginAccount.isEmpty) {
|
||||
gitbucket.core.html.index(getRecentActivities(),
|
||||
getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||
)
|
||||
} else {
|
||||
val loginUserName = loginAccount.get.userName
|
||||
val loginUserGroups = getGroupsByUserName(loginUserName)
|
||||
var visibleOwnerSet : Set[String] = Set(loginUserName)
|
||||
|
||||
visibleOwnerSet ++= loginUserGroups
|
||||
|
||||
gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet),
|
||||
getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||
)
|
||||
context.loginAccount.map { account =>
|
||||
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||
gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet), Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true))
|
||||
}.getOrElse {
|
||||
gitbucket.core.html.index(getRecentActivities(), getVisibleRepositories(None, withoutPhysicalInfo = true), Nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +69,15 @@ trait IndexControllerBase extends ControllerBase {
|
||||
xml.feed(getRecentActivities())
|
||||
}
|
||||
|
||||
get("/sidebar-collapse"){
|
||||
if(params("collapse") == "true"){
|
||||
session.setAttribute("sidebar-collapse", "true")
|
||||
} else {
|
||||
session.setAttribute("sidebar-collapse", null)
|
||||
}
|
||||
Ok()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account information into HttpSession and redirect.
|
||||
*/
|
||||
@@ -108,29 +105,35 @@ trait IndexControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/_user/proposals")(usersOnly {
|
||||
contentType = formats("json")
|
||||
val user = params("user").toBoolean
|
||||
val group = params("group").toBoolean
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray)
|
||||
Map("options" -> (
|
||||
getAllUsers(false)
|
||||
.withFilter { t => (user, group) match {
|
||||
case (true, true) => true
|
||||
case (true, false) => !t.isGroupAccount
|
||||
case (false, true) => t.isGroupAccount
|
||||
case (false, false) => false
|
||||
}}.map { t => t.userName }
|
||||
))
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* JSON API for checking user existence.
|
||||
* JSON API for checking user or group existence.
|
||||
* Returns a single string which is any of "group", "user" or "".
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserName(params("userName")).map { account =>
|
||||
if(params.get("userOnly").isDefined) !account.isGroupAccount else true
|
||||
} getOrElse false
|
||||
if(account.isGroupAccount) "group" else "user"
|
||||
} getOrElse ""
|
||||
})
|
||||
|
||||
// TODO Move to RepositoryViwerController?
|
||||
post("/search", searchForm){ form =>
|
||||
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
|
||||
}
|
||||
|
||||
// TODO Move to RepositoryViwerController?
|
||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
||||
val page = try {
|
||||
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if(i <= 0) 1 else i
|
||||
} catch {
|
||||
@@ -139,23 +142,31 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
target.toLowerCase match {
|
||||
case "issue" => gitbucket.core.search.html.issues(
|
||||
countFiles(repository.owner, repository.name, query),
|
||||
searchIssues(repository.owner, repository.name, query),
|
||||
countWikiPages(repository.owner, repository.name, query),
|
||||
if(query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil,
|
||||
query, page, repository)
|
||||
|
||||
case "wiki" => gitbucket.core.search.html.wiki(
|
||||
countFiles(repository.owner, repository.name, query),
|
||||
countIssues(repository.owner, repository.name, query),
|
||||
searchWikiPages(repository.owner, repository.name, query),
|
||||
if(query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
||||
query, page, repository)
|
||||
|
||||
case _ => gitbucket.core.search.html.code(
|
||||
searchFiles(repository.owner, repository.name, query),
|
||||
countIssues(repository.owner, repository.name, query),
|
||||
countWikiPages(repository.owner, repository.name, query),
|
||||
if(query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
||||
query, page, repository)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
get("/search"){
|
||||
val query = params.getOrElse("query", "").trim.toLowerCase
|
||||
val visibleRepositories = getVisibleRepositories(context.loginAccount, None)
|
||||
val repositories = visibleRepositories.filter { repository =>
|
||||
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
|
||||
}
|
||||
context.loginAccount.map { account =>
|
||||
gitbucket.core.search.html.repositories(query, repositories, Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true))
|
||||
}.getOrElse {
|
||||
gitbucket.core.search.html.repositories(query, repositories, visibleRepositories, Nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,24 +2,45 @@ package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.html
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.Markdown
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import org.scalatra.Ok
|
||||
|
||||
|
||||
class IssuesController extends IssuesControllerBase
|
||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with ActivityService
|
||||
with HandleCommentService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with PullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with CommitsService
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
|
||||
self: IssuesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with ActivityService
|
||||
with HandleCommentService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with PullRequestService
|
||||
with WebHookIssueCommentService =>
|
||||
|
||||
case class IssueCreateForm(title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
|
||||
@@ -67,72 +88,77 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
_,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted,
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
isEditable(repository),
|
||||
isManageable(repository),
|
||||
repository)
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
html.create(
|
||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted,
|
||||
if(isEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
html.create(
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestones(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
isManageable(repository),
|
||||
repository)
|
||||
}
|
||||
}
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
||||
val userName = context.loginAccount.get.userName
|
||||
if(isEditable(repository)){ // TODO Should this check is provided by authenticator?
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
val manageable = isManageable(repository)
|
||||
val userName = context.loginAccount.get.userName
|
||||
|
||||
// insert issue
|
||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
||||
if(writable) form.assignedUserName else None,
|
||||
if(writable) form.milestoneId else None)
|
||||
// insert issue
|
||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
||||
if (manageable) form.assignedUserName else None,
|
||||
if (manageable) form.milestoneId else None)
|
||||
|
||||
// insert labels
|
||||
if(writable){
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(owner, name, issueId, label.labelId)
|
||||
// insert labels
|
||||
if (manageable) {
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(owner, name, issueId, label.labelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// record activity
|
||||
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
||||
// record activity
|
||||
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
||||
|
||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
|
||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
|
||||
|
||||
// call web hooks
|
||||
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
|
||||
// call web hooks
|
||||
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
|
||||
|
||||
// notifications
|
||||
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
|
||||
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||
// notifications
|
||||
Notifier().toNotify(repository, issue, form.content.getOrElse("")) {
|
||||
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/${issueId}")
|
||||
}
|
||||
redirect(s"/${owner}/${name}/issues/${issueId}")
|
||||
}
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
if(isEditableContent(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
@@ -147,7 +173,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
if(isEditableContent(owner, name, issue.openedUserName)){
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||
// extract references and create refer comment
|
||||
@@ -161,7 +187,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||
@@ -171,7 +197,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
|
||||
@@ -182,7 +208,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||
updateComment(comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized()
|
||||
@@ -193,7 +219,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||
Ok(deleteComment(comment.commentId))
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
@@ -202,7 +228,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
||||
if(isEditableContent(x.userName, x.repositoryName, x.openedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||
} getOrElse {
|
||||
@@ -218,7 +244,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.openedUserName)
|
||||
hasWritePermission = true
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -229,7 +255,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||
if(isEditableContent(x.userName, x.repositoryName, x.commentedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
@@ -244,7 +270,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
|
||||
hasWritePermission = true
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -253,32 +279,32 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/new/label")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository =>
|
||||
val labelNames = params("labelNames").split(",")
|
||||
val labels = getLabels(repository.owner, repository.name).filter(x => labelNames.contains(x.labelName))
|
||||
html.labellist(labels)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
|
||||
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
|
||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
|
||||
milestoneId("milestoneId").map { milestoneId =>
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
@@ -288,7 +314,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
} getOrElse Ok()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
||||
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
|
||||
defining(params.get("value")){ action =>
|
||||
action match {
|
||||
case Some("open") => executeBatch(repository) { issueId =>
|
||||
@@ -306,7 +332,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
|
||||
post("/:owner/:repository/issues/batchedit/label")(writableUsersOnly { repository =>
|
||||
params("value").toIntOpt.map{ labelId =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
@@ -316,7 +342,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
|
||||
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
|
||||
defining(assignedUserName("value")){ value =>
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
||||
@@ -324,7 +350,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
|
||||
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
|
||||
defining(milestoneId("value")){ value =>
|
||||
executeBatch(repository) {
|
||||
updateMilestoneId(repository.owner, repository.name, _, value)
|
||||
@@ -346,9 +372,6 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||
params("checked").split(',') map(_.toInt) foreach execute
|
||||
params("from") match {
|
||||
@@ -359,8 +382,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = Keys.Session.Issues(owner, repoName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
@@ -369,18 +391,42 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
"issues",
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
page,
|
||||
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
|
||||
(getCollaborators(owner, repoName) :+ owner).sorted
|
||||
} else {
|
||||
getCollaborators(owner, repoName)
|
||||
},
|
||||
getAssignableUserNames(owner, repoName),
|
||||
getMilestones(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
isEditable(repository),
|
||||
isManageable(repository))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can manage issues.
|
||||
*/
|
||||
private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can post issues.
|
||||
*/
|
||||
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
repository.repository.options.issuesOption match {
|
||||
case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
|
||||
case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
|
||||
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
case "DISABLE" => false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an issue or a comment is editable by a logged-in user.
|
||||
*/
|
||||
private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = {
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.labels.html
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
@@ -10,11 +10,11 @@ import org.scalatra.Ok
|
||||
|
||||
class LabelsController extends LabelsControllerBase
|
||||
with LabelsService with IssuesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
|
||||
trait LabelsControllerBase extends ControllerBase {
|
||||
self: LabelsService with IssuesService with RepositoryService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
case class LabelForm(labelName: String, color: String)
|
||||
|
||||
@@ -29,40 +29,40 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
getLabels(repository.owner, repository.name),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
||||
ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository =>
|
||||
html.edit(None, repository)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(writableUsersOnly { (form, repository) =>
|
||||
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
||||
html.label(
|
||||
getLabel(repository.owner, repository.name, labelId).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository =>
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||
html.edit(Some(label), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(writableUsersOnly { (form, repository) =>
|
||||
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
|
||||
html.label(
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository =>
|
||||
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
||||
Ok()
|
||||
})
|
||||
|
||||
@@ -2,17 +2,17 @@ package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.milestones.html
|
||||
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
|
||||
class MilestonesController extends MilestonesControllerBase
|
||||
with MilestonesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
|
||||
trait MilestonesControllerBase extends ControllerBase {
|
||||
self: MilestonesService with RepositoryService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
||||
|
||||
@@ -27,25 +27,25 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
params.getOrElse("state", "open"),
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/new")(collaboratorsOnly {
|
||||
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
|
||||
html.edit(None, _)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/milestones/new", milestoneForm)(collaboratorsOnly { (form, repository) =>
|
||||
post("/:owner/:repository/issues/milestones/new", milestoneForm)(writableUsersOnly { (form, repository) =>
|
||||
createMilestone(repository.owner, repository.name, form.title, form.description, form.dueDate)
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.map{ milestoneId =>
|
||||
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(collaboratorsOnly { (form, repository) =>
|
||||
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableUsersOnly { (form, repository) =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
|
||||
@@ -54,7 +54,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/close")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/close")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
closeMilestone(milestone)
|
||||
@@ -63,7 +63,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/open")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/open")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
openMilestone(milestone)
|
||||
@@ -72,7 +72,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(writableUsersOnly { repository =>
|
||||
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
|
||||
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
|
||||
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
|
||||
|
||||
@@ -6,36 +6,32 @@ import gitbucket.core.service.CommitStatusService
|
||||
import gitbucket.core.service.MergeService
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service.PullRequestService._
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.PersonIdent
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
|
||||
class PullRequestsController extends PullRequestsControllerBase
|
||||
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
||||
with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with CommitsService with ActivityService with WebHookPullRequestService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
with CommitStatusService with MergeService with ProtectedBranchService
|
||||
|
||||
|
||||
trait PullRequestsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
||||
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator
|
||||
with CommitStatusService with MergeService with ProtectedBranchService =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
||||
|
||||
val pullRequestForm = mapping(
|
||||
"title" -> trim(label("Title" , text(required, maxlength(100)))),
|
||||
"content" -> trim(label("Content", optional(text()))),
|
||||
@@ -94,12 +90,13 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
|
||||
.sortWith((a, b) => a.registeredDate before b.registeredDate),
|
||||
getIssueLabels(owner, name, issueId),
|
||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
getLabels(owner, name),
|
||||
commits,
|
||||
diffs,
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
isEditable(repository),
|
||||
isManageable(repository),
|
||||
repository,
|
||||
flash.toMap.map(f => f._1 -> f._2.toString))
|
||||
}
|
||||
@@ -115,7 +112,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val hasConflict = LockUtil.lock(s"${owner}/${name}"){
|
||||
checkConflict(owner, name, pullreq.branch, issueId)
|
||||
}
|
||||
val hasMergePermission = hasWritePermission(owner, name, context.loginAccount)
|
||||
val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount)
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
|
||||
val mergeStatus = PullRequestService.MergeStatus(
|
||||
hasConflict = hasConflict,
|
||||
@@ -125,7 +122,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
needStatusCheck = context.loginAccount.map{ u =>
|
||||
branchProtection.needStatusCheck(u.userName)
|
||||
}.getOrElse(true),
|
||||
hasUpdatePermission = hasWritePermission(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) &&
|
||||
hasUpdatePermission = hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) &&
|
||||
context.loginAccount.map{ u =>
|
||||
!getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch).needStatusCheck(u.userName)
|
||||
}.getOrElse(false),
|
||||
@@ -141,7 +138,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/pull/:id/delete/*")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/pull/:id/delete/*")(writableUsersOnly { repository =>
|
||||
params("id").toIntOpt.map { issueId =>
|
||||
val branchName = multiParams("splat").head
|
||||
val userName = context.loginAccount.get.userName
|
||||
@@ -156,24 +153,24 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/update_branch")(referrersOnly { baseRepository =>
|
||||
post("/:owner/:repository/pull/:id/update_branch")(writableUsersOnly { baseRepository =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
loginAccount <- context.loginAccount
|
||||
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
owner = pullreq.requestUserName
|
||||
name = pullreq.requestRepositoryName
|
||||
if hasWritePermission(owner, name, context.loginAccount)
|
||||
if hasDeveloperRole(owner, name, context.loginAccount)
|
||||
} yield {
|
||||
val repository = getRepository(owner, name).get
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||
if(branchProtection.needStatusCheck(loginAccount.userName)){
|
||||
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
|
||||
} else {
|
||||
val repository = getRepository(owner, name).get
|
||||
LockUtil.lock(s"${owner}/${name}"){
|
||||
val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){
|
||||
pullreq.branch
|
||||
}else{
|
||||
} else {
|
||||
s"${pullreq.userName}:${pullreq.branch}"
|
||||
}
|
||||
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet
|
||||
@@ -187,11 +184,10 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
|
||||
using(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
|
||||
// after update branch
|
||||
|
||||
val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}")
|
||||
val commits = git.log.addRange(oldId, newCommitId).call.iterator.asScala.map(c => new JGitUtil.CommitInfo(c)).toList
|
||||
|
||||
commits.foreach{ commit =>
|
||||
commits.foreach { commit =>
|
||||
if(!existIds.contains(commit.id)){
|
||||
createIssueComment(owner, name, commit)
|
||||
}
|
||||
@@ -220,12 +216,13 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
|
||||
}
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
|
||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
|
||||
params("id").toIntOpt.flatMap { issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
@@ -374,8 +371,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
forkedRepository,
|
||||
originRepository,
|
||||
forkedRepository,
|
||||
hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount),
|
||||
(getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.isGroupAccount) Nil else List(originRepository.owner))).sorted,
|
||||
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
|
||||
getAssignableUserNames(originRepository.owner, originRepository.name),
|
||||
getMilestones(originRepository.owner, originRepository.name),
|
||||
getLabels(originRepository.owner, originRepository.name)
|
||||
)
|
||||
@@ -389,7 +386,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { forkedRepository =>
|
||||
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
|
||||
val Seq(origin, forked) = multiParams("splat")
|
||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
|
||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
|
||||
@@ -419,64 +416,68 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
|
||||
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
||||
val loginUserName = context.loginAccount.get.userName
|
||||
val manageable = isManageable(repository)
|
||||
val editable = isEditable(repository)
|
||||
|
||||
val issueId = createIssue(
|
||||
owner = repository.owner,
|
||||
repository = repository.name,
|
||||
loginUser = loginUserName,
|
||||
title = form.title,
|
||||
content = form.content,
|
||||
assignedUserName = if(writable) form.assignedUserName else None,
|
||||
milestoneId = if(writable) form.milestoneId else None,
|
||||
isPullRequest = true)
|
||||
if(editable) {
|
||||
val loginUserName = context.loginAccount.get.userName
|
||||
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
issueId = issueId,
|
||||
originBranch = form.targetBranch,
|
||||
requestUserName = form.requestUserName,
|
||||
requestRepositoryName = form.requestRepositoryName,
|
||||
requestBranch = form.requestBranch,
|
||||
commitIdFrom = form.commitIdFrom,
|
||||
commitIdTo = form.commitIdTo)
|
||||
val issueId = createIssue(
|
||||
owner = repository.owner,
|
||||
repository = repository.name,
|
||||
loginUser = loginUserName,
|
||||
title = form.title,
|
||||
content = form.content,
|
||||
assignedUserName = if (manageable) form.assignedUserName else None,
|
||||
milestoneId = if (manageable) form.milestoneId else None,
|
||||
isPullRequest = true)
|
||||
|
||||
// insert labels
|
||||
if(writable){
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
issueId = issueId,
|
||||
originBranch = form.targetBranch,
|
||||
requestUserName = form.requestUserName,
|
||||
requestRepositoryName = form.requestRepositoryName,
|
||||
requestBranch = form.requestBranch,
|
||||
commitIdFrom = form.commitIdFrom,
|
||||
commitIdTo = form.commitIdTo)
|
||||
|
||||
// insert labels
|
||||
if (manageable) {
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetch requested branch
|
||||
fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
|
||||
// fetch requested branch
|
||||
fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
|
||||
|
||||
// record activity
|
||||
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
|
||||
// record activity
|
||||
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||
|
||||
getIssue(owner, name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
|
||||
getIssue(owner, name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
|
||||
|
||||
// notifications
|
||||
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
|
||||
Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
|
||||
// notifications
|
||||
Notifier().toNotify(repository, issue, form.content.getOrElse("")) {
|
||||
Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
} else Unauthorized()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -494,30 +495,9 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
(defaultOwner, value)
|
||||
}
|
||||
|
||||
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) =
|
||||
using(
|
||||
Git.open(getRepositoryDir(userName, repositoryName)),
|
||||
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
|
||||
){ (oldGit, newGit) =>
|
||||
val oldId = oldGit.getRepository.resolve(branch)
|
||||
val newId = newGit.getRepository.resolve(requestCommitId)
|
||||
|
||||
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
|
||||
new CommitInfo(revCommit)
|
||||
}.toList.splitWith { (commit1, commit2) =>
|
||||
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}
|
||||
|
||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
||||
|
||||
(commits, diffs)
|
||||
}
|
||||
|
||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = Keys.Session.Pulls(owner, repoName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
@@ -526,18 +506,34 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
"pulls",
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
page,
|
||||
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
|
||||
(getCollaborators(owner, repoName) :+ owner).sorted
|
||||
} else {
|
||||
getCollaborators(owner, repoName)
|
||||
},
|
||||
getAssignableUserNames(owner, repoName),
|
||||
getMilestones(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open" ), true, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
isEditable(repository),
|
||||
isManageable(repository))
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can manage pull requests.
|
||||
*/
|
||||
private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can post pull requests.
|
||||
*/
|
||||
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
repository.repository.options.issuesOption match {
|
||||
case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
|
||||
case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
|
||||
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
case "DISABLE" => false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,22 +31,20 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
repositoryName: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
enableIssues: Boolean,
|
||||
issuesOption: String,
|
||||
externalIssuesUrl: Option[String],
|
||||
enableWiki: Boolean,
|
||||
allowWikiEditing: Boolean,
|
||||
wikiOption: String,
|
||||
externalWikiUrl: Option[String],
|
||||
allowFork: Boolean
|
||||
)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"repositoryName" -> trim(label("Repository Name" , text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||
"repositoryName" -> trim(label("Repository Name" , text(required, maxlength(100), identifier, renameRepositoryName))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type" , boolean())),
|
||||
"enableIssues" -> trim(label("Enable Issues" , boolean())),
|
||||
"issuesOption" -> trim(label("Issues Option" , text(required, featureOption))),
|
||||
"externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))),
|
||||
"enableWiki" -> trim(label("Enable Wiki" , boolean())),
|
||||
"allowWikiEditing" -> trim(label("Allow Wiki Editing" , boolean())),
|
||||
"wikiOption" -> trim(label("Wiki Option" , text(required, featureOption))),
|
||||
"externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))),
|
||||
"allowFork" -> trim(label("Allow Forking" , boolean()))
|
||||
)(OptionsForm.apply)
|
||||
@@ -58,12 +56,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100))))
|
||||
)(DefaultBranchForm.apply)
|
||||
|
||||
// for collaborator addition
|
||||
case class CollaboratorForm(userName: String)
|
||||
|
||||
val collaboratorForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, collaborator)))
|
||||
)(CollaboratorForm.apply)
|
||||
// // for collaborator addition
|
||||
// case class CollaboratorForm(userName: String)
|
||||
//
|
||||
// val collaboratorForm = mapping(
|
||||
// "userName" -> trim(label("Username", text(required, collaborator)))
|
||||
// )(CollaboratorForm.apply)
|
||||
|
||||
// for web hook url addition
|
||||
case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
|
||||
@@ -109,10 +107,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
repository.repository.parentUserName.map { _ =>
|
||||
repository.repository.isPrivate
|
||||
} getOrElse form.isPrivate,
|
||||
form.enableIssues,
|
||||
form.issuesOption,
|
||||
form.externalIssuesUrl,
|
||||
form.enableWiki,
|
||||
form.allowWikiEditing,
|
||||
form.wikiOption,
|
||||
form.externalWikiUrl,
|
||||
form.allowFork
|
||||
)
|
||||
@@ -178,22 +175,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
|
||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
||||
addCollaborator(repository.owner, repository.name, form.userName)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
|
||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
||||
removeCollaborator(repository.owner, repository.name, params("name"))
|
||||
post("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
||||
val collaborators = params("collaborators")
|
||||
removeCollaborators(repository.owner, repository.name)
|
||||
collaborators.split(",").withFilter(_.nonEmpty).map { collaborator =>
|
||||
val userName :: role :: Nil = collaborator.split(":").toList
|
||||
addCollaborator(repository.owner, repository.name, userName, role)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
||||
})
|
||||
@@ -251,7 +238,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token)
|
||||
val dummyPayload = {
|
||||
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||
val commits = if(repository.commitCount == 0) List.empty else git.log
|
||||
val commits = if(JGitUtil.isEmpty(git)) List.empty else git.log
|
||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||
.setMaxCount(4)
|
||||
.call.iterator.asScala.map(new CommitInfo(_)).toList
|
||||
@@ -397,20 +384,20 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the collaborator name.
|
||||
*/
|
||||
private def collaborator: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) if(x.isGroupAccount)
|
||||
=> Some("User does not exist.")
|
||||
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
|
||||
=> Some("User can access this repository already.")
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * Provides Constraint to validate the collaborator name.
|
||||
// */
|
||||
// private def collaborator: Constraint = new Constraint(){
|
||||
// override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
// getAccountByUserName(value) match {
|
||||
// case None => Some("User does not exist.")
|
||||
//// case Some(x) if(x.isGroupAccount)
|
||||
//// => Some("User does not exist.")
|
||||
// case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
|
||||
// => Some(value + " is repository owner.") // TODO also group members?
|
||||
// case _ => None
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Duplicate check for the rename repository name.
|
||||
@@ -424,6 +411,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private def featureOption: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
||||
if(Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the repository transfer user.
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.scalatra._
|
||||
|
||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
*/
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService =>
|
||||
|
||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||
@@ -110,7 +110,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||
enableTaskList = params("enableTaskList").toBoolean,
|
||||
enableAnchor = false,
|
||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
||||
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -151,13 +151,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||
logs.splitWith{ (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/new/*")(writableUsersOnly { repository =>
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
||||
@@ -165,7 +165,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
protectedBranch)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/edit/*")(writableUsersOnly { repository =>
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||
|
||||
@@ -181,7 +181,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/remove/*")(writableUsersOnly { repository =>
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
@@ -194,7 +194,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||
post("/:owner/:repository/create", editorForm)(writableUsersOnly { (form, repository) =>
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
@@ -211,7 +211,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}")
|
||||
})
|
||||
|
||||
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||
post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) =>
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
@@ -232,7 +232,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}")
|
||||
})
|
||||
|
||||
post("/:owner/:repository/remove", deleteForm)(collaboratorsOnly { (form, repository) =>
|
||||
post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) =>
|
||||
commitFile(repository, form.branch, form.path, None, Some(form.fileName), "", "",
|
||||
form.message.getOrElse(s"Delete ${form.fileName}"))
|
||||
|
||||
@@ -275,7 +275,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
html.blob(id, repository, path.split("/").toList,
|
||||
JGitUtil.getContentInfo(git, path, objectId),
|
||||
new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)),
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
request.paths(2) == "blame")
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -328,8 +328,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
getCommitComments(repository.owner, repository.name, id, false),
|
||||
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
getCommitComments(repository.owner, repository.name, id, true),
|
||||
repository, diffs, oldCommitId, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,7 +358,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
html.commentform(
|
||||
commitId = id,
|
||||
fileName, oldLineNumber, newLineNumber, issueId,
|
||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
repository = repository
|
||||
)
|
||||
})
|
||||
@@ -374,7 +374,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
||||
}
|
||||
helper.html.commitcomment(comment, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
helper.html.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
@@ -393,7 +393,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
|
||||
hasWritePermission = true
|
||||
)
|
||||
))
|
||||
}
|
||||
@@ -437,13 +437,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
.map(br => (br, getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId), protectedBranches.contains(br.name)))
|
||||
.reverse
|
||||
|
||||
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
html.branches(branches, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a branch.
|
||||
*/
|
||||
post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
|
||||
post("/:owner/:repository/branches")(writableUsersOnly { repository =>
|
||||
val newBranchName = params.getOrElse("new", halt(400))
|
||||
val fromBranchName = params.getOrElse("from", halt(400))
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
@@ -461,7 +461,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Deletes branch.
|
||||
*/
|
||||
get("/:owner/:repository/delete/*")(collaboratorsOnly { repository =>
|
||||
get("/:owner/:repository/delete/*")(writableUsersOnly { repository =>
|
||||
val branchName = multiParams("splat").head
|
||||
val userName = context.loginAccount.get.userName
|
||||
if(repository.repository.defaultBranch != branchName){
|
||||
@@ -546,10 +546,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* @return HTML of the file list
|
||||
*/
|
||||
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
|
||||
if(repository.commitCount == 0){
|
||||
html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} else {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
if(JGitUtil.isEmpty(git)){
|
||||
html.guide(repository, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
} else {
|
||||
// get specified commit
|
||||
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
|
||||
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
|
||||
@@ -569,9 +569,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
html.files(revision, repository,
|
||||
if(path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
JGitUtil.getCommitCount(repository.owner, repository.name, revision),
|
||||
files,
|
||||
readme,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),
|
||||
flash.get("info"), flash.get("error"))
|
||||
flash.get("info"),
|
||||
flash.get("error")
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -593,14 +598,18 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val headName = s"refs/heads/${branch}"
|
||||
val headTip = git.getRepository.resolve(headName)
|
||||
|
||||
JGitUtil.processTree(git, headTip){ (path, tree) =>
|
||||
val permission = JGitUtil.processTree(git, headTip){ (path, tree) =>
|
||||
// Add all entries except the editing file
|
||||
if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){
|
||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||
}
|
||||
}
|
||||
// Retrieve permission if file exists to keep it
|
||||
oldPath.collect { case x if x == path => tree.getEntryFileMode.getBits }
|
||||
}.flatten.headOption
|
||||
|
||||
newPath.foreach { newPath =>
|
||||
builder.add(JGitUtil.createDirCacheEntry(newPath, FileMode.REGULAR_FILE,
|
||||
builder.add(JGitUtil.createDirCacheEntry(newPath,
|
||||
permission.map { bits => FileMode.fromBits(bits) } getOrElse FileMode.REGULAR_FILE,
|
||||
inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
|
||||
}
|
||||
builder.finish()
|
||||
@@ -687,7 +696,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -279,13 +279,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
} else {
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
// Update COLLABORATOR for group repositories
|
||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
removeCollaborators(form.groupName, repositoryName)
|
||||
members.foreach { case (userName, isManager) =>
|
||||
addCollaborator(form.groupName, repositoryName, userName)
|
||||
}
|
||||
}
|
||||
// // Update COLLABORATOR for group repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// removeCollaborators(form.groupName, repositoryName)
|
||||
// members.foreach { case (userName, isManager) =>
|
||||
// addCollaborator(form.groupName, repositoryName, userName)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
|
||||
@@ -14,10 +14,10 @@ import org.scalatra.i18n.Messages
|
||||
|
||||
class WikiController extends WikiControllerBase
|
||||
with WikiService with RepositoryService with AccountService with ActivityService
|
||||
with CollaboratorsAuthenticator with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator
|
||||
|
||||
trait WikiControllerBase extends ControllerBase {
|
||||
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
||||
self: WikiService with RepositoryService with ActivityService with ReadableUsersAuthenticator with ReferrerAuthenticator =>
|
||||
|
||||
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String)
|
||||
|
||||
@@ -62,7 +62,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
|
||||
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(referrersOnly { repository =>
|
||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
@@ -101,7 +101,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_revert/:commitId")(referrersOnly { repository =>
|
||||
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
@@ -114,14 +114,14 @@ trait WikiControllerBase extends ControllerBase {
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_edit")(referrersOnly { repository =>
|
||||
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_edit", editForm)(referrersOnly { (form, repository) =>
|
||||
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
|
||||
if(isEditable(repository)){
|
||||
defining(context.loginAccount.get){ loginAccount =>
|
||||
saveWikiPage(
|
||||
@@ -146,13 +146,13 @@ trait WikiControllerBase extends ControllerBase {
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_new")(referrersOnly { repository =>
|
||||
get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
html.edit("", None, repository)
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_new", newForm)(referrersOnly { (form, repository) =>
|
||||
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
|
||||
if(isEditable(repository)){
|
||||
defining(context.loginAccount.get){ loginAccount =>
|
||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
||||
@@ -170,7 +170,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_delete")(referrersOnly { repository =>
|
||||
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
|
||||
if(isEditable(repository)){
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
|
||||
@@ -182,7 +182,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
}
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
|
||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||
html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository))
|
||||
})
|
||||
@@ -190,7 +190,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
JGitUtil.getCommitLog(git, "master") match {
|
||||
case Right((logs, hasNext)) => html.history(None, logs, repository)
|
||||
case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
}
|
||||
@@ -240,9 +240,13 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
||||
|
||||
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean =
|
||||
repository.repository.options.allowWikiEditing || (
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
repository.repository.options.wikiOption match {
|
||||
case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
|
||||
case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
|
||||
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
case "DISABLE" => false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
|
||||
|
||||
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
|
||||
val collaboratorName = column[String]("COLLABORATOR_NAME")
|
||||
def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply)
|
||||
val role = column[String]("ROLE")
|
||||
def * = (userName, repositoryName, collaboratorName, role) <> (Collaborator.tupled, Collaborator.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, collaborator: String) =
|
||||
byRepository(owner, repository) && (collaboratorName === collaborator.bind)
|
||||
@@ -17,5 +18,23 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
|
||||
case class Collaborator(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
collaboratorName: String
|
||||
collaboratorName: String,
|
||||
role: String
|
||||
)
|
||||
|
||||
sealed abstract class Role(val name: String)
|
||||
|
||||
object Role {
|
||||
object ADMIN extends Role("ADMIN")
|
||||
object DEVELOPER extends Role("DEVELOPER")
|
||||
object GUEST extends Role("GUEST")
|
||||
|
||||
// val values: Vector[Permission] = Vector(ADMIN, WRITE, READ)
|
||||
//
|
||||
// private val map: Map[String, Permission] = values.map(enum => enum.name -> enum).toMap
|
||||
//
|
||||
// def apply(name: String): Permission = map(name)
|
||||
//
|
||||
// def valueOf(name: String): Option[Permission] = map.get(name)
|
||||
|
||||
}
|
||||
@@ -17,17 +17,16 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
||||
val parentUserName = column[String]("PARENT_USER_NAME")
|
||||
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
||||
val enableIssues = column[Boolean]("ENABLE_ISSUES")
|
||||
val issuesOption = column[String]("ISSUES_OPTION")
|
||||
val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL")
|
||||
val enableWiki = column[Boolean]("ENABLE_WIKI")
|
||||
val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING")
|
||||
val wikiOption = column[String]("WIKI_OPTION")
|
||||
val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL")
|
||||
val allowFork = column[Boolean]("ALLOW_FORK")
|
||||
|
||||
def * = (
|
||||
(userName, repositoryName, isPrivate, description.?, defaultBranch,
|
||||
registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?),
|
||||
(enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?, allowFork)
|
||||
(issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork)
|
||||
).shaped <> (
|
||||
{ case (repository, options) =>
|
||||
Repository(
|
||||
@@ -85,10 +84,9 @@ case class Repository(
|
||||
)
|
||||
|
||||
case class RepositoryOptions(
|
||||
enableIssues: Boolean,
|
||||
issuesOption: String,
|
||||
externalIssuesUrl: Option[String],
|
||||
enableWiki: Boolean,
|
||||
allowWikiEditing: Boolean,
|
||||
wikiOption: String,
|
||||
externalWikiUrl: Option[String],
|
||||
allowFork: Boolean
|
||||
)
|
||||
|
||||
@@ -23,5 +23,5 @@ class UserNameSuggestionProvider extends SuggestionProvider {
|
||||
override def values(repository: RepositoryInfo): Seq[String] = Nil
|
||||
override def template(implicit context: Context): String = "'@' + value"
|
||||
override def additionalScript(implicit context: Context): String =
|
||||
s"""$$.get('${context.path}/_user/proposals', { query: '' }, function (data) { user = data.options; });"""
|
||||
s"""$$.get('${context.path}/_user/proposals', { query: '', user: true, group: false }, function (data) { user = data.options; });"""
|
||||
}
|
||||
@@ -42,12 +42,18 @@ trait CommitsService {
|
||||
updatedDate = currentDate,
|
||||
issueId = issueId)
|
||||
|
||||
def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])(implicit s: Session): Unit =
|
||||
CommitComments.filter(_.byPrimaryKey(commentId))
|
||||
.map { t =>
|
||||
(t.commitId, t.oldLine, t.newLine)
|
||||
}.update(commitId, oldLine, newLine)
|
||||
|
||||
def updateCommitComment(commentId: Int, content: String)(implicit s: Session) =
|
||||
CommitComments
|
||||
.filter (_.byPrimaryKey(commentId))
|
||||
.map { t =>
|
||||
t.content -> t.updatedDate
|
||||
}.update (content, currentDate)
|
||||
t.content -> t.updatedDate
|
||||
}.update (content, currentDate)
|
||||
|
||||
def deleteCommitComment(commentId: Int)(implicit s: Session) =
|
||||
CommitComments filter (_.byPrimaryKey(commentId)) delete
|
||||
|
||||
@@ -13,7 +13,7 @@ trait HandleCommentService {
|
||||
with WebHookService with WebHookIssueCommentService with WebHookPullRequestService =>
|
||||
|
||||
/**
|
||||
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
||||
* @see [[https://github.com/gitbucket/gitbucket/wiki/CommentAction]]
|
||||
*/
|
||||
def handleComment(issue: Issue, content: Option[String], repository: RepositoryService.RepositoryInfo, actionOpt: Option[String])
|
||||
(implicit context: Context, s: Session) = {
|
||||
@@ -54,18 +54,20 @@ trait HandleCommentService {
|
||||
|
||||
// call web hooks
|
||||
action match {
|
||||
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
|
||||
case Some(act) => val webHookAction = act match {
|
||||
case "open" => "opened"
|
||||
case "reopen" => "reopened"
|
||||
case "close" => "closed"
|
||||
case _ => act
|
||||
}
|
||||
if(issue.isPullRequest){
|
||||
case None => commentId.map { commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
|
||||
case Some(act) => {
|
||||
val webHookAction = act match {
|
||||
case "open" => "opened"
|
||||
case "reopen" => "reopened"
|
||||
case "close" => "closed"
|
||||
case _ => act
|
||||
}
|
||||
if (issue.isPullRequest) {
|
||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
|
||||
} else {
|
||||
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notifications
|
||||
|
||||
@@ -13,8 +13,9 @@ import scala.slick.jdbc.{StaticQuery => Q}
|
||||
import Q.interpolation
|
||||
|
||||
|
||||
|
||||
trait IssuesService {
|
||||
self: AccountService =>
|
||||
self: AccountService with RepositoryService =>
|
||||
import IssuesService._
|
||||
|
||||
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
||||
@@ -34,6 +35,10 @@ trait IssuesService {
|
||||
.map{ case ((t1, t2), t3) => (t1, t2, t3) }
|
||||
.list
|
||||
|
||||
def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = {
|
||||
getCommentsForApi(owner, repository, issueId).collectFirst { case (comment, account, _) if comment.action == "merged" => (comment, account) }
|
||||
}
|
||||
|
||||
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
|
||||
if (commentId forall (_.isDigit))
|
||||
IssueComments filter { t =>
|
||||
@@ -433,6 +438,11 @@ trait IssuesService {
|
||||
}
|
||||
}
|
||||
|
||||
def getAssignableUserNames(owner: String, repository: String)(implicit s: Session): List[String] = {
|
||||
(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)) :::
|
||||
(if (getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).distinct.sorted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object IssuesService {
|
||||
|
||||
@@ -86,7 +86,7 @@ object ProtectedBranchService {
|
||||
def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)(implicit session: Session): Option[String] = {
|
||||
if(enabled){
|
||||
command.getType() match {
|
||||
case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
|
||||
case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
|
||||
Some("Cannot force-push to a protected branch")
|
||||
case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||
unSuccessedContexts(command.getNewId.name) match {
|
||||
@@ -98,7 +98,7 @@ object ProtectedBranchService {
|
||||
Some("Cannot delete a protected branch")
|
||||
case _ => None
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{Account, Issue, PullRequest, WebHook, CommitStatus, CommitState}
|
||||
import difflib.{Delta, DiffUtils}
|
||||
import gitbucket.core.model.{Session => _, _}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo}
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.eclipse.jgit.api.Git
|
||||
import profile.simple._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
trait PullRequestService { self: IssuesService =>
|
||||
|
||||
trait PullRequestService { self: IssuesService with CommitsService =>
|
||||
import PullRequestService._
|
||||
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int)
|
||||
@@ -111,9 +121,26 @@ trait PullRequestService { self: IssuesService =>
|
||||
def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit =
|
||||
getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq =>
|
||||
if(Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run){
|
||||
// Update the git repository
|
||||
val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest(
|
||||
pullreq.userName, pullreq.repositoryName, pullreq.branch, pullreq.issueId,
|
||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)
|
||||
|
||||
// Collect comment positions
|
||||
val positions = getCommitComments(pullreq.userName, pullreq.repositoryName, pullreq.commitIdTo, true)
|
||||
.collect {
|
||||
case CommitComment(_, _, _, commentId, _, _, Some(file), None, Some(newLine), _, _, _) => (file, commentId, Right(newLine))
|
||||
case CommitComment(_, _, _, commentId, _, _, Some(file), Some(oldLine), None, _, _, _) => (file, commentId, Left(oldLine))
|
||||
}
|
||||
.groupBy { case (file, _, _) => file }
|
||||
.map { case (file, comments) => file ->
|
||||
comments.map { case (_, commentId, lineNumber) => (commentId, lineNumber) }
|
||||
}
|
||||
|
||||
// Update comments position
|
||||
updatePullRequestCommentPositions(positions, pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo, commitIdTo)
|
||||
|
||||
// Update commit id in the PULL_REQUEST table
|
||||
updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
|
||||
}
|
||||
}
|
||||
@@ -137,6 +164,78 @@ trait PullRequestService { self: IssuesService =>
|
||||
.firstOption
|
||||
}
|
||||
}
|
||||
|
||||
private def updatePullRequestCommentPositions(positions: Map[String, Seq[(Int, Either[Int, Int])]], userName: String, repositoryName: String,
|
||||
oldCommitId: String, newCommitId: String)(implicit s: Session): Unit = {
|
||||
|
||||
val (_, diffs) = getRequestCompareInfo(userName, repositoryName, oldCommitId, userName, repositoryName, newCommitId)
|
||||
|
||||
val patchs = positions.map { case (file, _) =>
|
||||
diffs.find(x => x.oldPath == file).map { diff =>
|
||||
(diff.oldContent, diff.newContent) match {
|
||||
case (Some(oldContent), Some(newContent)) => {
|
||||
val oldLines = oldContent.replace("\r\n", "\n").split("\n")
|
||||
val newLines = newContent.replace("\r\n", "\n").split("\n")
|
||||
file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava))
|
||||
}
|
||||
case _ =>
|
||||
file -> None
|
||||
}
|
||||
}.getOrElse {
|
||||
file -> None
|
||||
}
|
||||
}
|
||||
|
||||
positions.foreach { case (file, comments) =>
|
||||
patchs(file) match {
|
||||
case Some(patch) => file -> comments.foreach { case (commentId, lineNumber) => lineNumber match {
|
||||
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||
case Right(newLine) =>
|
||||
var counter = newLine
|
||||
patch.getDeltas.asScala.filter(_.getOriginal.getPosition < newLine).foreach { delta =>
|
||||
delta.getType match {
|
||||
case Delta.TYPE.CHANGE =>
|
||||
if(delta.getOriginal.getPosition <= newLine - 1 && newLine <= delta.getOriginal.getPosition + delta.getRevised.getLines.size){
|
||||
counter = -1
|
||||
} else {
|
||||
counter = counter + (delta.getRevised.getLines.size - delta.getOriginal.getLines.size)
|
||||
}
|
||||
case Delta.TYPE.INSERT => counter = counter + delta.getRevised.getLines.size
|
||||
case Delta.TYPE.DELETE => counter = counter - delta.getOriginal.getLines.size
|
||||
}
|
||||
}
|
||||
if(counter >= 0){
|
||||
updateCommitCommentPosition(commentId, newCommitId, None, Some(counter))
|
||||
}
|
||||
}}
|
||||
case _ => comments.foreach { case (commentId, lineNumber) => lineNumber match {
|
||||
case Right(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||
case Left(newLine) => updateCommitCommentPosition(commentId, newCommitId, None, Some(newLine))
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) =
|
||||
using(
|
||||
Git.open(getRepositoryDir(userName, repositoryName)),
|
||||
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
|
||||
){ (oldGit, newGit) =>
|
||||
val oldId = oldGit.getRepository.resolve(branch)
|
||||
val newId = newGit.getRepository.resolve(requestCommitId)
|
||||
|
||||
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
|
||||
new CommitInfo(revCommit)
|
||||
}.toList.splitWith { (commit1, commit2) =>
|
||||
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}
|
||||
|
||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
||||
|
||||
(commits, diffs)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object PullRequestService {
|
||||
|
||||
@@ -21,12 +21,12 @@ trait RepositoryCreationService {
|
||||
// Insert to the database at first
|
||||
insertRepository(name, owner, description, isPrivate)
|
||||
|
||||
// Add collaborators for group repository
|
||||
if(ownerAccount.isGroupAccount){
|
||||
getGroupMembers(owner).foreach { member =>
|
||||
addCollaborator(owner, name, member.userName)
|
||||
}
|
||||
}
|
||||
// // Add collaborators for group repository
|
||||
// if(ownerAccount.isGroupAccount){
|
||||
// getGroupMembers(owner).foreach { member =>
|
||||
// addCollaborator(owner, name, member.userName)
|
||||
// }
|
||||
// }
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(owner, name)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account}
|
||||
import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account, Role}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import profile.simple._
|
||||
@@ -38,10 +38,9 @@ trait RepositoryService { self: AccountService =>
|
||||
parentUserName = parentUserName,
|
||||
parentRepositoryName = parentRepositoryName,
|
||||
options = RepositoryOptions(
|
||||
enableIssues = true,
|
||||
issuesOption = "PUBLIC", // TODO DISABLE for the forked repository?
|
||||
externalIssuesUrl = None,
|
||||
enableWiki = true,
|
||||
allowWikiEditing = true,
|
||||
wikiOption = "PUBLIC", // TODO DISABLE for the forked repository?
|
||||
externalWikiUrl = None,
|
||||
allowFork = true
|
||||
)
|
||||
@@ -124,11 +123,8 @@ trait RepositoryService { self: AccountService =>
|
||||
repositoryName = newRepositoryName
|
||||
)) :_*)
|
||||
|
||||
if(account.isGroupAccount){
|
||||
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
|
||||
} else {
|
||||
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
}
|
||||
// TODO Drop transfered owner from collaborators?
|
||||
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
|
||||
// Update activity messages
|
||||
Activities.filter { t =>
|
||||
@@ -227,7 +223,7 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repositories without private repository that user does not have access right.
|
||||
* Returns the repositories except private repository that user does not have access right.
|
||||
* Include public repository, private own repository and private but collaborator repository.
|
||||
*
|
||||
* @param userName the user name of collaborator
|
||||
@@ -236,8 +232,10 @@ trait RepositoryService { self: AccountService =>
|
||||
def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = {
|
||||
Repositories.filter { t1 =>
|
||||
(t1.isPrivate === false.bind) ||
|
||||
(t1.userName === userName.bind) ||
|
||||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists)
|
||||
(t1.userName === userName.bind) || (t1.userName in (GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) ||
|
||||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) &&
|
||||
((t2.collaboratorName === userName.bind) || (t2.collaboratorName in GroupMembers.filter(_.userName === userName.bind).map(_.groupName)))
|
||||
} exists)
|
||||
}.sortBy(_.lastActivityDate desc).map{ t =>
|
||||
(t.userName, t.repositoryName)
|
||||
}.list
|
||||
@@ -246,8 +244,10 @@ trait RepositoryService { self: AccountService =>
|
||||
def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)
|
||||
(implicit s: Session): List[RepositoryInfo] = {
|
||||
Repositories.filter { t1 =>
|
||||
(t1.userName === userName.bind) ||
|
||||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists)
|
||||
(t1.userName === userName.bind) || (t1.userName in (GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) ||
|
||||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) &&
|
||||
((t2.collaboratorName === userName.bind) || (t2.collaboratorName in GroupMembers.filter(_.userName === userName.bind).map(_.groupName)))
|
||||
} exists)
|
||||
}.sortBy(_.lastActivityDate desc).list.map{ repository =>
|
||||
new RepositoryInfo(
|
||||
if(withoutPhysicalInfo){
|
||||
@@ -282,8 +282,13 @@ trait RepositoryService { self: AccountService =>
|
||||
case Some(x) if(x.isAdmin) => Repositories
|
||||
// for Normal Users
|
||||
case Some(x) if(!x.isAdmin) =>
|
||||
Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) ||
|
||||
(Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists)
|
||||
Repositories filter { t =>
|
||||
(t.isPrivate === false.bind) || (t.userName === x.userName) ||
|
||||
(t.userName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName)) ||
|
||||
(Collaborators.filter { t2 =>
|
||||
t2.byRepository(t.userName, t.repositoryName) &&
|
||||
((t2.collaboratorName === x.userName.bind) || (t2.collaboratorName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName)))
|
||||
} exists)
|
||||
}
|
||||
// for Guests
|
||||
case None => Repositories filter(_.isPrivate === false.bind)
|
||||
@@ -323,12 +328,12 @@ trait RepositoryService { self: AccountService =>
|
||||
*/
|
||||
def saveRepositoryOptions(userName: String, repositoryName: String,
|
||||
description: Option[String], isPrivate: Boolean,
|
||||
enableIssues: Boolean, externalIssuesUrl: Option[String],
|
||||
enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String],
|
||||
issuesOption: String, externalIssuesUrl: Option[String],
|
||||
wikiOption: String, externalWikiUrl: Option[String],
|
||||
allowFork: Boolean)(implicit s: Session): Unit =
|
||||
Repositories.filter(_.byRepository(userName, repositoryName))
|
||||
.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, allowFork, currentDate)
|
||||
.map { r => (r.description.?, r.isPrivate, r.issuesOption, r.externalIssuesUrl.?, r.wikiOption, r.externalWikiUrl.?, r.allowFork, r.updatedDate) }
|
||||
.update (description, isPrivate, issuesOption, externalIssuesUrl, wikiOption, externalWikiUrl, allowFork, currentDate)
|
||||
|
||||
def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
|
||||
defaultBranch: String)(implicit s: Session): Unit =
|
||||
@@ -337,49 +342,64 @@ trait RepositoryService { self: AccountService =>
|
||||
.update (defaultBranch)
|
||||
|
||||
/**
|
||||
* Add collaborator to the repository.
|
||||
*
|
||||
* @param userName the user name of the repository owner
|
||||
* @param repositoryName the repository name
|
||||
* @param collaboratorName the collaborator name
|
||||
* Add collaborator (user or group) to the repository.
|
||||
*/
|
||||
def addCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit =
|
||||
Collaborators insert Collaborator(userName, repositoryName, collaboratorName)
|
||||
|
||||
/**
|
||||
* Remove collaborator from the repository.
|
||||
*
|
||||
* @param userName the user name of the repository owner
|
||||
* @param repositoryName the repository name
|
||||
* @param collaboratorName the collaborator name
|
||||
*/
|
||||
def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit =
|
||||
Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete
|
||||
def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, role: String)(implicit s: Session): Unit =
|
||||
Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role)
|
||||
|
||||
/**
|
||||
* Remove all collaborators from the repository.
|
||||
*
|
||||
* @param userName the user name of the repository owner
|
||||
* @param repositoryName the repository name
|
||||
*/
|
||||
def removeCollaborators(userName: String, repositoryName: String)(implicit s: Session): Unit =
|
||||
Collaborators.filter(_.byRepository(userName, repositoryName)).delete
|
||||
|
||||
/**
|
||||
* Returns the list of collaborators name which is sorted with ascending order.
|
||||
*
|
||||
* @param userName the user name of the repository owner
|
||||
* @param repositoryName the repository name
|
||||
* @return the list of collaborators name
|
||||
* Returns the list of collaborators name (user name or group name) which is sorted with ascending order.
|
||||
*/
|
||||
def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[String] =
|
||||
Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list
|
||||
def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[(Collaborator, Boolean)] =
|
||||
Collaborators
|
||||
.innerJoin(Accounts).on(_.collaboratorName === _.userName)
|
||||
.filter { case (t1, t2) => t1.byRepository(userName, repositoryName) }
|
||||
.map { case (t1, t2) => (t1, t2.groupAccount) }
|
||||
.sortBy { case (t1, t2) => t1.collaboratorName }
|
||||
.list
|
||||
|
||||
def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
/**
|
||||
* Returns the list of all collaborator name and permission which is sorted with ascending order.
|
||||
* If a group is added as a collaborator, this method returns users who are belong to that group.
|
||||
*/
|
||||
def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[Role] = Nil)(implicit s: Session): List[String] = {
|
||||
val q1 = Collaborators
|
||||
.innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) }
|
||||
.filter { case (t1, t2) => t1.byRepository(userName, repositoryName) }
|
||||
.map { case (t1, t2) => (t1.collaboratorName, t1.role) }
|
||||
|
||||
val q2 = Collaborators
|
||||
.innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) }
|
||||
.innerJoin(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName }
|
||||
.filter { case ((t1, t2), t3) => t1.byRepository(userName, repositoryName) }
|
||||
.map { case ((t1, t2), t3) => (t3.userName, t1.role) }
|
||||
|
||||
q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1)
|
||||
}
|
||||
|
||||
|
||||
def hasDeveloperRole(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(getCollaborators(owner, repository).contains(a.userName)) => 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)) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
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(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST)).contains(a.userName)) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
@@ -401,26 +421,20 @@ trait RepositoryService { self: AccountService =>
|
||||
object RepositoryService {
|
||||
|
||||
case class RepositoryInfo(owner: String, name: String, repository: Repository,
|
||||
issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int,
|
||||
issueCount: Int, pullCount: Int, forkedCount: Int,
|
||||
branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]) {
|
||||
|
||||
/**
|
||||
* Creates instance with issue count and pull request count.
|
||||
*/
|
||||
def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) =
|
||||
this(
|
||||
repo.owner, repo.name, model,
|
||||
issueCount, pullCount, repo.commitCount, forkedCount,
|
||||
repo.branchList, repo.tags, managers)
|
||||
this(repo.owner, repo.name, model, issueCount, pullCount, forkedCount, repo.branchList, repo.tags, managers)
|
||||
|
||||
/**
|
||||
* Creates instance without issue count and pull request count.
|
||||
*/
|
||||
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) =
|
||||
this(
|
||||
repo.owner, repo.name, model,
|
||||
0, 0, repo.commitCount, forkedCount,
|
||||
repo.branchList, repo.tags, managers)
|
||||
this(repo.owner, repo.name, model, 0, 0, forkedCount, repo.branchList, repo.tags, managers)
|
||||
|
||||
def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name)
|
||||
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
|
||||
@@ -434,7 +448,6 @@ object RepositoryService {
|
||||
|
||||
(id, path.substring(id.length).stripPrefix("/"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
|
||||
|
||||
@@ -11,7 +11,7 @@ import Implicits.request2Session
|
||||
* It may be called many times in one request, so each method stores
|
||||
* its result into the cache which available during a request.
|
||||
*/
|
||||
trait RequestCache extends SystemSettingsService with AccountService with IssuesService {
|
||||
trait RequestCache extends SystemSettingsService with AccountService with IssuesService with RepositoryService {
|
||||
|
||||
private implicit def context2Session(implicit context: Context): Session =
|
||||
request2Session(context.request)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import fr.brouillard.oss.security.xhub.XHub
|
||||
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
|
||||
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent, CommitComment}
|
||||
import gitbucket.core.model.{Account, CommitComment, Issue, IssueComment, PullRequest, WebHook, WebHookEvent}
|
||||
import gitbucket.core.model.Profile._
|
||||
import org.apache.http.client.utils.URLEncodedUtils
|
||||
import profile.simple._
|
||||
@@ -16,6 +18,7 @@ import org.apache.http.message.BasicNameValuePair
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.concurrent._
|
||||
import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
@@ -33,15 +36,15 @@ trait WebHookService {
|
||||
def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(WebHook, Set[WebHook.Event])] =
|
||||
WebHooks.filter(_.byRepository(owner, repository))
|
||||
.innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) }
|
||||
.map{ case (w,t) => w -> t.event }
|
||||
.map { case (w,t) => w -> t.event }
|
||||
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url)
|
||||
|
||||
/** get All WebHook informations of repository event */
|
||||
def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] =
|
||||
WebHooks.filter(_.byRepository(owner, repository))
|
||||
.innerJoin(WebHookEvents).on { (wh, whe) => whe.byWebHook(wh) }
|
||||
.filter{ case (wh, whe) => whe.event === event.bind}
|
||||
.map{ case (wh, whe) => wh }
|
||||
.filter { case (wh, whe) => whe.event === event.bind}
|
||||
.map { case (wh, whe) => wh }
|
||||
.list.distinct
|
||||
|
||||
/** get All WebHook information from repository to url */
|
||||
@@ -49,12 +52,12 @@ trait WebHookService {
|
||||
WebHooks
|
||||
.filter(_.byPrimaryKey(owner, repository, url))
|
||||
.innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) }
|
||||
.map{ case (w,t) => w -> t.event }
|
||||
.map { case (w,t) => w -> t.event }
|
||||
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
|
||||
|
||||
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
|
||||
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
|
||||
WebHooks insert WebHook(owner, repository, url, ctype, token)
|
||||
events.toSet.map{ event: WebHook.Event =>
|
||||
events.map { event: WebHook.Event =>
|
||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
@@ -62,7 +65,7 @@ trait WebHookService {
|
||||
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
|
||||
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token))
|
||||
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
|
||||
events.toSet.map{ event: WebHook.Event =>
|
||||
events.map { event: WebHook.Event =>
|
||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
@@ -81,7 +84,7 @@ trait WebHookService {
|
||||
def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload)
|
||||
(implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = {
|
||||
import org.apache.http.impl.client.HttpClientBuilder
|
||||
import ExecutionContext.Implicits.global
|
||||
import ExecutionContext.Implicits.global // TODO Shouldn't use the default execution context
|
||||
import org.apache.http.protocol.HttpContext
|
||||
import org.apache.http.client.methods.HttpPost
|
||||
|
||||
@@ -91,7 +94,7 @@ trait WebHookService {
|
||||
webHooks.map { webHook =>
|
||||
val reqPromise = Promise[HttpRequest]
|
||||
val f = Future {
|
||||
val itcp = new org.apache.http.HttpRequestInterceptor{
|
||||
val itcp = new org.apache.http.HttpRequestInterceptor {
|
||||
def process(res: HttpRequest, ctx: HttpContext): Unit = {
|
||||
reqPromise.success(res)
|
||||
}
|
||||
@@ -129,8 +132,8 @@ trait WebHookService {
|
||||
httpPost.releaseConnection()
|
||||
logger.debug(s"end web hook invocation for ${webHook}")
|
||||
res
|
||||
}catch{
|
||||
case e:Throwable => {
|
||||
} catch {
|
||||
case e: Throwable => {
|
||||
if(!reqPromise.isCompleted){
|
||||
reqPromise.failure(e)
|
||||
}
|
||||
@@ -168,11 +171,11 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
} yield {
|
||||
WebHookIssuesPayload(
|
||||
action = action,
|
||||
number = issue.issueId,
|
||||
repository = ApiRepository(repository, ApiUser(repoOwner)),
|
||||
issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)),
|
||||
sender = ApiUser(sender))
|
||||
action = action,
|
||||
number = issue.issueId,
|
||||
repository = ApiRepository(repository, ApiUser(repoOwner)),
|
||||
issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)),
|
||||
sender = ApiUser(sender))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +201,9 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
headOwner = headOwner,
|
||||
baseRepository = repository,
|
||||
baseOwner = baseOwner,
|
||||
sender = sender)
|
||||
sender = sender,
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issueId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,7 +242,10 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
headOwner = headOwner,
|
||||
baseRepository = baseRepo,
|
||||
baseOwner = baseOwner,
|
||||
sender = sender)
|
||||
sender = sender,
|
||||
mergedComment = getMergedComment(baseRepo.owner, baseRepo.name, issue.issueId)
|
||||
)
|
||||
|
||||
callWebHook(WebHook.PullRequest, webHooks, payload)
|
||||
}
|
||||
}
|
||||
@@ -267,7 +275,9 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
|
||||
headOwner = headOwner,
|
||||
baseRepository = repository,
|
||||
baseOwner = baseOwner,
|
||||
sender = sender)
|
||||
sender = sender,
|
||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,11 +375,21 @@ object WebHookService {
|
||||
headOwner: Account,
|
||||
baseRepository: RepositoryInfo,
|
||||
baseOwner: Account,
|
||||
sender: Account): WebHookPullRequestPayload = {
|
||||
sender: Account,
|
||||
mergedComment: Option[(IssueComment, Account)]): WebHookPullRequestPayload = {
|
||||
|
||||
val headRepoPayload = ApiRepository(headRepository, headOwner)
|
||||
val baseRepoPayload = ApiRepository(baseRepository, baseOwner)
|
||||
val senderPayload = ApiUser(sender)
|
||||
val pr = ApiPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, ApiUser(issueUser))
|
||||
val pr = ApiPullRequest(
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = headRepoPayload,
|
||||
baseRepo = baseRepoPayload,
|
||||
user = ApiUser(issueUser),
|
||||
mergedComment = mergedComment
|
||||
)
|
||||
|
||||
WebHookPullRequestPayload(
|
||||
action = action,
|
||||
number = issue.issueId,
|
||||
@@ -389,7 +409,7 @@ object WebHookService {
|
||||
sender: ApiUser
|
||||
) extends WebHookPayload
|
||||
|
||||
object WebHookIssueCommentPayload{
|
||||
object WebHookIssueCommentPayload {
|
||||
def apply(
|
||||
issue: Issue,
|
||||
issueUser: Account,
|
||||
@@ -415,28 +435,42 @@ object WebHookService {
|
||||
sender: ApiUser
|
||||
) extends WebHookPayload
|
||||
|
||||
object WebHookPullRequestReviewCommentPayload{
|
||||
object WebHookPullRequestReviewCommentPayload {
|
||||
def apply(
|
||||
action: String,
|
||||
comment: CommitComment,
|
||||
issue: Issue,
|
||||
issueUser: Account,
|
||||
pullRequest: PullRequest,
|
||||
headRepository: RepositoryInfo,
|
||||
headOwner: Account,
|
||||
baseRepository: RepositoryInfo,
|
||||
baseOwner: Account,
|
||||
sender: Account
|
||||
) : WebHookPullRequestReviewCommentPayload = {
|
||||
action: String,
|
||||
comment: CommitComment,
|
||||
issue: Issue,
|
||||
issueUser: Account,
|
||||
pullRequest: PullRequest,
|
||||
headRepository: RepositoryInfo,
|
||||
headOwner: Account,
|
||||
baseRepository: RepositoryInfo,
|
||||
baseOwner: Account,
|
||||
sender: Account,
|
||||
mergedComment: Option[(IssueComment, Account)]
|
||||
) : WebHookPullRequestReviewCommentPayload = {
|
||||
val headRepoPayload = ApiRepository(headRepository, headOwner)
|
||||
val baseRepoPayload = ApiRepository(baseRepository, baseOwner)
|
||||
val senderPayload = ApiUser(sender)
|
||||
|
||||
WebHookPullRequestReviewCommentPayload(
|
||||
action = action,
|
||||
comment = ApiPullRequestReviewComment(comment, senderPayload, RepositoryName(baseRepository), issue.issueId),
|
||||
pull_request = ApiPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, ApiUser(issueUser)),
|
||||
repository = baseRepoPayload,
|
||||
sender = senderPayload)
|
||||
action = action,
|
||||
comment = ApiPullRequestReviewComment(
|
||||
comment = comment,
|
||||
commentedUser = senderPayload,
|
||||
repositoryName = RepositoryName(baseRepository),
|
||||
issueId = issue.issueId
|
||||
),
|
||||
pull_request = ApiPullRequest(
|
||||
issue = issue,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = headRepoPayload,
|
||||
baseRepo = baseRepoPayload,
|
||||
user = ApiUser(issueUser),
|
||||
mergedComment = mergedComment
|
||||
),
|
||||
repository = baseRepoPayload,
|
||||
sender = senderPayload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
|
||||
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
|
||||
account <- authenticate(settings, username, password)
|
||||
} yield if(isUpdating || repository.repository.isPrivate){
|
||||
if(hasWritePermission(repository.owner, repository.name, Some(account))){
|
||||
if(hasDeveloperRole(repository.owner, repository.name, Some(account))){
|
||||
request.setAttribute(Keys.Request.UserName, account.userName)
|
||||
true
|
||||
} else false
|
||||
|
||||
@@ -110,7 +110,7 @@ import scala.collection.JavaConverters._
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session)
|
||||
extends PostReceiveHook with PreReceiveHook
|
||||
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService
|
||||
with WebHookPullRequestService with ProtectedBranchService {
|
||||
with WebHookPullRequestService with CommitsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
private var existIds: Seq[String] = Nil
|
||||
@@ -139,6 +139,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||
try {
|
||||
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
JGitUtil.removeCache(git)
|
||||
|
||||
val pushedIds = scala.collection.mutable.Set[String]()
|
||||
commands.asScala.foreach { command =>
|
||||
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
||||
|
||||
@@ -36,7 +36,7 @@ abstract class GitCommand extends Command with SessionAware {
|
||||
override def run(): Unit = {
|
||||
authUser match {
|
||||
case Some(authUser) =>
|
||||
Database() withSession { implicit session =>
|
||||
Database() withTransaction { implicit session =>
|
||||
try {
|
||||
runTask(authUser)
|
||||
callback.onExit(0)
|
||||
@@ -92,7 +92,7 @@ abstract class DefaultGitCommand(val owner: String, val repoName: String) extend
|
||||
protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo)
|
||||
(implicit session: Session): Boolean =
|
||||
getAccountByUserName(username) match {
|
||||
case Some(account) => hasWritePermission(repositoryInfo.owner, repositoryInfo.name, Some(account))
|
||||
case Some(account) => hasDeveloperRole(repositoryInfo.owner, repositoryInfo.name, Some(account))
|
||||
case None => false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{RepositoryService, AccountService}
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.model.Role
|
||||
import RepositoryService.RepositoryInfo
|
||||
import Implicits._
|
||||
import ControlUtil._
|
||||
@@ -40,9 +41,9 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
|
||||
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(getGroupMembers(repository.owner).exists { member =>
|
||||
member.userName == x.userName && member.isManager == true
|
||||
}) => action(repository)
|
||||
// TODO Repository management is allowed for only group managers?
|
||||
case Some(x) if(getGroupMembers(repository.owner).exists { m => m.userName == x.userName && m.isManager == true }) => action(repository)
|
||||
case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq(Role.ADMIN)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -86,32 +87,9 @@ trait AdminAuthenticator { self: ControllerBase =>
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows only collaborators and administrators.
|
||||
* Allows only guests and signed in users who can access the repository.
|
||||
*/
|
||||
trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =>
|
||||
protected def collaboratorsOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def collaboratorsOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
{
|
||||
defining(request.paths){ paths =>
|
||||
getRepository(paths(0), paths(1)).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows only the repository owner (or manager for group repository) and administrators.
|
||||
*/
|
||||
trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
|
||||
trait ReferrerAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
|
||||
protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
@@ -125,7 +103,8 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
}
|
||||
@@ -136,9 +115,9 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows only signed in users which can access the repository.
|
||||
* Allows only signed in users who have read permission for the repository.
|
||||
*/
|
||||
trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =>
|
||||
trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
|
||||
protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
@@ -150,7 +129,32 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(!repository.repository.isPrivate) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows only signed in users who have write permission for the repository.
|
||||
*/
|
||||
trait WritableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
|
||||
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) = {
|
||||
{
|
||||
defining(request.paths){ paths =>
|
||||
getRepository(paths(0), paths(1)).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq(Role.ADMIN, Role.DEVELOPER)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
|
||||
@@ -9,7 +9,10 @@ import scala.collection.mutable.ListBuffer
|
||||
|
||||
/**
|
||||
* Provides implicit class which extends java.sql.Connection.
|
||||
* This is used in automatic migration in [[servlet.AutoUpdateListener]].
|
||||
* This is used in following points:
|
||||
*
|
||||
* - Automatic migration in [[gitbucket.core.servlet.InitializeListener]]
|
||||
* - Data importing / exporting in [[gitbucket.core.controller.SystemSettingsController]] and [[gitbucket.core.controller.FileUploadController]]
|
||||
*/
|
||||
object JDBCUtil {
|
||||
|
||||
@@ -71,8 +74,6 @@ object JDBCUtil {
|
||||
val bytes = new scala.Array[Byte](1024 * 8)
|
||||
var stringLiteral = false
|
||||
|
||||
var count = 0
|
||||
|
||||
while({ length = in.read(bytes); length != -1 }){
|
||||
for(i <- 0 to length - 1){
|
||||
val c = bytes(i)
|
||||
@@ -81,13 +82,19 @@ object JDBCUtil {
|
||||
}
|
||||
if(c == ';' && !stringLiteral){
|
||||
val sql = new String(out.toByteArray, "UTF-8")
|
||||
conn.update(sql)
|
||||
conn.update(sql.trim)
|
||||
out = new ByteArrayOutputStream()
|
||||
} else {
|
||||
out.write(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val remain = out.toByteArray
|
||||
if(remain.length != 0){
|
||||
val sql = new String(remain, "UTF-8")
|
||||
conn.update(sql.trim)
|
||||
}
|
||||
}
|
||||
conn.commit()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.eclipse.jgit.api.Git
|
||||
import Directory._
|
||||
import StringUtil._
|
||||
import ControlUtil._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConverters._
|
||||
import org.eclipse.jgit.lib._
|
||||
@@ -16,7 +17,11 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
import java.util.Date
|
||||
import org.eclipse.jgit.api.errors.{JGitInternalException, InvalidRefNameException, RefAlreadyExistsException, NoHeadException}
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Consumer
|
||||
|
||||
import org.cache2k.{Cache2kBuilder, CacheEntry}
|
||||
import org.eclipse.jgit.api.errors.{InvalidRefNameException, JGitInternalException, NoHeadException, RefAlreadyExistsException}
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@@ -32,14 +37,11 @@ object JGitUtil {
|
||||
*
|
||||
* @param owner the user name of the repository owner
|
||||
* @param name the repository name
|
||||
* @param commitCount the commit count. If the repository has over 1000 commits then this property is 1001.
|
||||
* @param branchList the list of branch names
|
||||
* @param tags the list of tags
|
||||
*/
|
||||
case class RepositoryInfo(owner: String, name: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){
|
||||
def this(owner: String, name: String) = {
|
||||
this(owner, name, 0, Nil, Nil)
|
||||
}
|
||||
case class RepositoryInfo(owner: String, name: String, branchList: List[String], tags: List[TagInfo]){
|
||||
def this(owner: String, name: String) = this(owner, name, Nil, Nil)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,20 +171,54 @@ object JGitUtil {
|
||||
revWalk.dispose
|
||||
revCommit
|
||||
}
|
||||
|
||||
|
||||
private val cache = new Cache2kBuilder[String, Int]() {}
|
||||
.name("commit-count")
|
||||
.expireAfterWrite(24, TimeUnit.HOURS)
|
||||
.entryCapacity(10000)
|
||||
.build()
|
||||
|
||||
def removeCache(git: Git): Unit = {
|
||||
val dir = git.getRepository.getDirectory
|
||||
val keyPrefix = dir.getAbsolutePath + "@"
|
||||
|
||||
cache.forEach(new Consumer[CacheEntry[String, Int]] {
|
||||
override def accept(entry: CacheEntry[String, Int]): Unit = {
|
||||
if(entry.getKey.startsWith(keyPrefix)){
|
||||
cache.remove(entry.getKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of commits in the specified branch or commit.
|
||||
* If the specified branch has over 10000 commits, this method returns 100001.
|
||||
*/
|
||||
def getCommitCount(owner: String, repository: String, branch: String): Int = {
|
||||
val dir = getRepositoryDir(owner, repository)
|
||||
val key = dir.getAbsolutePath + "@" + branch
|
||||
val entry = cache.getEntry(key)
|
||||
|
||||
if(entry == null) {
|
||||
using(Git.open(dir)) { git =>
|
||||
val commitId = git.getRepository.resolve(branch)
|
||||
val commitCount = git.log.add(commitId).call.iterator.asScala.take(10001).size
|
||||
cache.put(key, commitCount)
|
||||
commitCount
|
||||
}
|
||||
} else {
|
||||
entry.getValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repository information. It contains branch names and tag names.
|
||||
*/
|
||||
def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = {
|
||||
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
||||
try {
|
||||
// get commit count
|
||||
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10001).sum
|
||||
|
||||
RepositoryInfo(
|
||||
owner, repository,
|
||||
// commit count
|
||||
commitCount,
|
||||
RepositoryInfo(owner, repository,
|
||||
// branches
|
||||
git.branchList.call.asScala.map { ref =>
|
||||
ref.getName.stripPrefix("refs/heads/")
|
||||
@@ -195,9 +231,7 @@ object JGitUtil {
|
||||
)
|
||||
} catch {
|
||||
// not initialized
|
||||
case e: NoHeadException => RepositoryInfo(
|
||||
owner, repository, 0, Nil, Nil)
|
||||
|
||||
case e: NoHeadException => RepositoryInfo(owner, repository, Nil, Nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,8 +246,8 @@ object JGitUtil {
|
||||
*/
|
||||
def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if(objectId==null) return Nil
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if(objectId == null) return Nil
|
||||
val revCommit = revWalk.parseCommit(objectId)
|
||||
|
||||
def useTreeWalk(rev:RevCommit)(f:TreeWalk => Any): Unit = if (path == ".") {
|
||||
@@ -255,14 +289,14 @@ object JGitUtil {
|
||||
revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, Option[String], RevCommit)] ={
|
||||
if(restList.isEmpty){
|
||||
result
|
||||
}else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty
|
||||
result ++ restList.map{ case (tuple, map) => tupleAdd(tuple, map.values.headOption.getOrElse(revCommit)) }
|
||||
}else{
|
||||
} else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty
|
||||
result ++ restList.map { case (tuple, map) => tupleAdd(tuple, map.values.headOption.getOrElse(revCommit)) }
|
||||
} else {
|
||||
val newCommit = revIterator.next
|
||||
val (thisTimeChecks,skips) = restList.partition{ case (tuple, parentsMap) => parentsMap.contains(newCommit) }
|
||||
val (thisTimeChecks,skips) = restList.partition { case (tuple, parentsMap) => parentsMap.contains(newCommit) }
|
||||
if(thisTimeChecks.isEmpty){
|
||||
findLastCommits(result, restList, revIterator)
|
||||
}else{
|
||||
} else {
|
||||
var nextRest = skips
|
||||
var nextResult = result
|
||||
// Map[(name, oid), (tuple, parentsMap)]
|
||||
@@ -270,20 +304,20 @@ object JGitUtil {
|
||||
lazy val newParentsMap = newCommit.getParents.map(_ -> newCommit).toMap
|
||||
useTreeWalk(newCommit){ walk =>
|
||||
while(walk.next){
|
||||
rest.remove(walk.getNameString -> walk.getObjectId(0)).map{ case (tuple, _) =>
|
||||
rest.remove(walk.getNameString -> walk.getObjectId(0)).map { case (tuple, _) =>
|
||||
if(newParentsMap.isEmpty){
|
||||
nextResult +:= tupleAdd(tuple, newCommit)
|
||||
}else{
|
||||
} else {
|
||||
nextRest +:= tuple -> newParentsMap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rest.values.map{ case (tuple, parentsMap) =>
|
||||
rest.values.map { case (tuple, parentsMap) =>
|
||||
val restParentsMap = parentsMap - newCommit
|
||||
if(restParentsMap.isEmpty){
|
||||
nextResult +:= tupleAdd(tuple, parentsMap(newCommit))
|
||||
}else{
|
||||
} else {
|
||||
nextRest +:= tuple -> restParentsMap
|
||||
}
|
||||
}
|
||||
@@ -295,7 +329,7 @@ object JGitUtil {
|
||||
var fileList: List[(ObjectId, FileMode, String, Option[String])] = Nil
|
||||
useTreeWalk(revCommit){ treeWalk =>
|
||||
while (treeWalk.next()) {
|
||||
val linkUrl =if (treeWalk.getFileMode(0) == FileMode.GITLINK) {
|
||||
val linkUrl = if (treeWalk.getFileMode(0) == FileMode.GITLINK) {
|
||||
getSubmodules(git, revCommit.getTree).find(_.path == treeWalk.getPathString).map(_.url)
|
||||
} else None
|
||||
fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getNameString, linkUrl)
|
||||
@@ -345,7 +379,7 @@ object JGitUtil {
|
||||
def getTreeId(git: Git, revision: String): Option[String] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if(objectId==null) return None
|
||||
if(objectId == null) return None
|
||||
val revCommit = revWalk.parseCommit(objectId)
|
||||
Some(revCommit.getTree.name)
|
||||
}
|
||||
@@ -357,7 +391,7 @@ object JGitUtil {
|
||||
def getAllFileListByTreeId(git: Git, treeId: String): List[String] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
val objectId = git.getRepository.resolve(treeId+"^{tree}")
|
||||
if(objectId==null) return Nil
|
||||
if(objectId == null) return Nil
|
||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
treeWalk.addTree(objectId)
|
||||
treeWalk.setRecursive(true)
|
||||
@@ -705,6 +739,8 @@ object JGitUtil {
|
||||
refUpdate.setNewObjectId(newHeadId)
|
||||
refUpdate.update()
|
||||
|
||||
removeCache(git)
|
||||
|
||||
newHeadId
|
||||
}
|
||||
|
||||
@@ -830,14 +866,16 @@ object JGitUtil {
|
||||
existIds.toSeq
|
||||
}
|
||||
|
||||
def processTree(git: Git, id: ObjectId)(f: (String, CanonicalTreeParser) => Unit) = {
|
||||
def processTree[T](git: Git, id: ObjectId)(f: (String, CanonicalTreeParser) => T): Seq[T] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
val index = treeWalk.addTree(revWalk.parseTree(id))
|
||||
treeWalk.setRecursive(true)
|
||||
val result = new collection.mutable.ListBuffer[T]()
|
||||
while(treeWalk.next){
|
||||
f(treeWalk.getPathString, treeWalk.getTree(index, classOf[CanonicalTreeParser]))
|
||||
result += f(treeWalk.getPathString, treeWalk.getTree(index, classOf[CanonicalTreeParser]))
|
||||
}
|
||||
result.toSeq
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -875,6 +913,7 @@ object JGitUtil {
|
||||
|
||||
/**
|
||||
* Returns the last modified commit of specified path
|
||||
*
|
||||
* @param git the Git object
|
||||
* @param startCommit the search base commit id
|
||||
* @param path the path of target file or directory
|
||||
@@ -957,6 +996,7 @@ object JGitUtil {
|
||||
|
||||
/**
|
||||
* Returns sha1
|
||||
*
|
||||
* @param owner repository owner
|
||||
* @param name repository name
|
||||
* @param revstr A git object references expression
|
||||
|
||||
@@ -22,8 +22,10 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
|
||||
(
|
||||
// individual repository's owner
|
||||
issue.userName ::
|
||||
// group members of group repository
|
||||
getGroupMembers(issue.userName).map(_.userName) :::
|
||||
// collaborators
|
||||
getCollaborators(issue.userName, issue.repositoryName) :::
|
||||
getCollaboratorUserNames(issue.userName, issue.repositoryName) :::
|
||||
// participants
|
||||
issue.openedUserName ::
|
||||
getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
|
||||
@@ -107,6 +109,9 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
}
|
||||
smtp.ssl.foreach { ssl =>
|
||||
email.setSSLOnConnect(ssl)
|
||||
if(ssl == true) {
|
||||
email.setSslSmtpPort(smtp.port.get.toString)
|
||||
}
|
||||
}
|
||||
smtp.fromAddress
|
||||
.map (_ -> smtp.fromName.getOrElse(context.loginAccount.get.userName))
|
||||
|
||||
@@ -73,7 +73,7 @@ trait LinkConverter { self: RequestCache =>
|
||||
}
|
||||
|
||||
// convert issue id to link
|
||||
.replaceBy(("(?<=(^|\\W))(GH-|" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r){ m =>
|
||||
.replaceBy(("(?<=(^|\\W))(GH-|(?<!&)" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r){ m =>
|
||||
val prefix = if(m.group(2) == "issue:") "#" else m.group(2)
|
||||
getIssue(repository.owner, repository.name, m.group(3)) match {
|
||||
case Some(issue) if(issue.isPullRequest) =>
|
||||
|
||||
@@ -44,7 +44,8 @@ object Markdown {
|
||||
val renderer = new GitBucketMarkedRenderer(options, repository,
|
||||
enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages)
|
||||
|
||||
helpers.decorateHtml(Marked.marked(source, options, renderer), repository)
|
||||
//helpers.decorateHtml(Marked.marked(source, options, renderer), repository)
|
||||
Marked.marked(source, options, renderer)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,11 +110,10 @@ object Markdown {
|
||||
override def text(text: String): String = {
|
||||
// convert commit id and username to link.
|
||||
val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "#", false) else text
|
||||
|
||||
// convert task list to checkbox.
|
||||
val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1
|
||||
|
||||
t2
|
||||
// decorate by TextDecorator plugins
|
||||
helpers.decorateHtml(t2, repository)
|
||||
}
|
||||
|
||||
override def link(href: String, title: String, text: String): String = {
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
</div>
|
||||
<a href="@context.path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
|
||||
<div style="width: 50%;">
|
||||
@gitbucket.core.helper.html.copy("generated-token-copy", tokenString){
|
||||
<input type="text" value="@tokenString" class="form-control input-sm" readonly>
|
||||
@gitbucket.core.helper.html.copy("generated-token", "generated-token-copy", tokenString){
|
||||
<input type="text" value="@tokenString" class="form-control input-sm" id="generated-token" readonly>
|
||||
}
|
||||
</div>
|
||||
<hr style="margin-top: 10px;">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<label class="strong">Members</label>
|
||||
<ul id="member-list" class="collaborator">
|
||||
</ul>
|
||||
@gitbucket.core.helper.html.account("memberName", 200)
|
||||
@gitbucket.core.helper.html.account("memberName", 200, true, false)
|
||||
<input type="button" class="btn btn-default" value="Add" id="addMember"/>
|
||||
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/>
|
||||
<div>
|
||||
@@ -43,10 +43,10 @@
|
||||
<fieldset class="border-top">
|
||||
@if(account.isDefined){
|
||||
<div class="pull-right">
|
||||
<a href="@helpers.url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a>
|
||||
<a href="@helpers.url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete group</a>
|
||||
</div>
|
||||
}
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create group} else {Update group}"/>
|
||||
@if(account.isDefined){
|
||||
<a href="@helpers.url(account.get.userName)" class="btn btn-default">Cancel</a>
|
||||
}
|
||||
@@ -80,15 +80,14 @@ $(function(){
|
||||
}
|
||||
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', {
|
||||
'userName': userName
|
||||
}, function(data, status){
|
||||
if(data == 'true'){
|
||||
addMemberHTML(userName, false);
|
||||
} else {
|
||||
$('#error-members').text('User does not exist.');
|
||||
}
|
||||
});
|
||||
$.post('@context.path/_user/existence', { 'userName': userName },
|
||||
function(data, status){
|
||||
if(data == 'user'){
|
||||
addMemberHTML(userName, false);
|
||||
} else {
|
||||
$('#error-members').text('User does not exist.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.remove', function(){
|
||||
@@ -137,4 +136,4 @@ $(function(){
|
||||
$('#members').val(members);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
@if(account.isGroupAccount){
|
||||
<li@if(active == "members"){ class="active"}><a href="@helpers.url(account.userName)?tab=members">Members</a></li>
|
||||
} else {
|
||||
<li@if(active == "activity"){ class="active"}><a href="@helpers.url(account.userName)?tab=activity">Public Activity</a></li>
|
||||
<li@if(active == "activity"){ class="active"}><a href="@helpers.url(account.userName)?tab=activity">Public activity</a></li>
|
||||
}
|
||||
@gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab =>
|
||||
@tab(account, context).map { link =>
|
||||
@@ -48,14 +48,14 @@
|
||||
@if(context.loginAccount.isDefined && context.loginAccount.get.userName == account.userName){
|
||||
<li class="pull-right">
|
||||
<div class="button-group">
|
||||
<a href="@helpers.url(account.userName)/_edit" class="btn btn-default">Edit Your Profile</a>
|
||||
<a href="@helpers.url(account.userName)/_edit" class="btn btn-default">Edit your profile</a>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if(context.loginAccount.isDefined && account.isGroupAccount && isGroupManager){
|
||||
<li class="pull-right">
|
||||
<div class="button-group">
|
||||
<a href="@helpers.url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a>
|
||||
<a href="@helpers.url(account.userName)/_editgroup" class="btn btn-default">Edit group</a>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<div class="sidebar">
|
||||
<ul class="sidebar-menu" id="system-admin-menu-container">
|
||||
<li@if(active=="users"){ class="active"}>
|
||||
<a href="@context.path/admin/users">User Management</a>
|
||||
<a href="@context.path/admin/users">User management</a>
|
||||
</li>
|
||||
<li@if(active=="system"){ class="active"}>
|
||||
<a href="@context.path/admin/system">System Settings</a>
|
||||
<a href="@context.path/admin/system">System settings</a>
|
||||
</li>
|
||||
<li@if(active=="plugins"){ class="active"}>
|
||||
<a href="@context.path/admin/plugins">Plugins</a>
|
||||
@@ -15,7 +15,7 @@
|
||||
<a href="@context.path/admin/data">Data export / import</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@context.path/console/login.jsp" target="_blank">H2 Console</a>
|
||||
<a href="@context.path/console/login.jsp" target="_blank">H2 console</a>
|
||||
</li>
|
||||
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
|
||||
@menu(context).map { link =>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("System Settings"){
|
||||
@gitbucket.core.html.main("System settings"){
|
||||
@gitbucket.core.admin.html.menu("system"){
|
||||
@gitbucket.core.helper.html.information(info)
|
||||
<form action="@context.path/admin/system" method="POST" validate="true" class="form-horizontal">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">System Settings</div>
|
||||
<div class="panel-heading strong">System settings</div>
|
||||
<div class="panel-body">
|
||||
<!--====================================================================-->
|
||||
<!-- System properties -->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@(account: Option[gitbucket.core.model.Account], error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main(if(account.isEmpty) "New User" else "Update User"){
|
||||
@gitbucket.core.html.main(if(account.isEmpty) "New user" else "Update user"){
|
||||
@gitbucket.core.admin.html.menu("users"){
|
||||
@gitbucket.core.helper.html.error(error)
|
||||
<form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newuser} else {@context.path/admin/users/@account.get.userName/_edituser}" validate="true">
|
||||
@@ -75,7 +75,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="border-top">
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/>
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create user} else {Update user}"/>
|
||||
<a href="@context.path/admin/users" class="btn btn-default">Cancel</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@(account: Option[gitbucket.core.model.Account], members: List[gitbucket.core.model.GroupMember])(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main(if(account.isEmpty) "New Group" else "Update Group"){
|
||||
@gitbucket.core.html.main(if(account.isEmpty) "New group" else "Update group"){
|
||||
@gitbucket.core.admin.html.menu("users"){
|
||||
<form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newgroup} else {@context.path/admin/users/@account.get.userName/_editgroup}" validate="true">
|
||||
<div class="row">
|
||||
@@ -34,7 +34,7 @@
|
||||
<label class="strong">Members</label>
|
||||
<ul id="member-list" class="collaborator">
|
||||
</ul>
|
||||
@gitbucket.core.helper.html.account("memberName", 200)
|
||||
@gitbucket.core.helper.html.account("memberName", 200, true, false)
|
||||
<input type="button" class="btn btn-default" value="Add" id="addMember"/>
|
||||
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/>
|
||||
<div>
|
||||
@@ -44,7 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="border-top">
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create group} else {Update group}"/>
|
||||
<a href="@context.path/admin/users" class="btn btn-default">Cancel</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
@@ -75,16 +75,14 @@ $(function(){
|
||||
}
|
||||
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', {
|
||||
'userName': userName,
|
||||
'userOnly': true
|
||||
}, function(data, status){
|
||||
if(data == 'true'){
|
||||
addMemberHTML(userName, false);
|
||||
} else {
|
||||
$('#error-members').text('User does not exist.');
|
||||
}
|
||||
});
|
||||
$.post('@context.path/_user/existence', { 'userName': userName },
|
||||
function(data, status){
|
||||
if(data == 'user'){
|
||||
addMemberHTML(userName, false);
|
||||
} else {
|
||||
$('#error-members').text('User does not exist.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.remove', function(){
|
||||
@@ -129,4 +127,4 @@ $(function(){
|
||||
$('#members').val(members);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
@gitbucket.core.html.main("Manage Users"){
|
||||
@gitbucket.core.admin.html.menu("users"){
|
||||
<div class="pull-right" style="margin-bottom: 4px;">
|
||||
<a href="@context.path/admin/users/_newuser" class="btn btn-default">New User</a>
|
||||
<a href="@context.path/admin/users/_newgroup" class="btn btn-default">New Group</a>
|
||||
<a href="@context.path/admin/users/_newuser" class="btn btn-default">New user</a>
|
||||
<a href="@context.path/admin/users/_newgroup" class="btn btn-default">New group</a>
|
||||
</div>
|
||||
<label for="includeRemoved">
|
||||
<input type="checkbox" id="includeRemoved" name="includeRemoved" @if(includeRemoved){checked}/>
|
||||
@@ -67,4 +67,4 @@ $(function(){
|
||||
location.href = '@context.path/admin/users?includeRemoved=' + this.checked;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
groups: List[String],
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Pull Requests"){
|
||||
@gitbucket.core.html.main("Pull requests"){
|
||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){
|
||||
@gitbucket.core.dashboard.html.tab("pulls")
|
||||
<div class="container">
|
||||
|
||||
@@ -12,21 +12,15 @@
|
||||
@if(userRepositories.isEmpty){
|
||||
<li>No repositories</li>
|
||||
} else {
|
||||
@defining(10){ max =>
|
||||
@userRepositories.zipWithIndex.map { case (repository, i) =>
|
||||
<li class="repo-link" style="@if(i > max - 1){display:none;}">
|
||||
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
|
||||
@userRepositories.zipWithIndex.map { case (repository, i) =>
|
||||
<li class="repo-link">
|
||||
@if(repository.owner == context.loginAccount.get.userName){
|
||||
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
|
||||
} else {
|
||||
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
@if(userRepositories.size > max){
|
||||
<li class="show-more">
|
||||
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
|
||||
</li>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -34,17 +28,11 @@
|
||||
@if(recentRepositories.isEmpty){
|
||||
<li>No repositories</li>
|
||||
} else {
|
||||
@defining(10){ max =>
|
||||
@recentRepositories.zipWithIndex.map { case (repository, i) =>
|
||||
<li class="repo-link" style="@if(i > max - 1){display:none;}">
|
||||
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
|
||||
</li>
|
||||
}
|
||||
@if(recentRepositories.size > max){
|
||||
<li class="show-more">
|
||||
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
|
||||
</li>
|
||||
}
|
||||
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
|
||||
@recentRepositories.zipWithIndex.map { case (repository, i) =>
|
||||
<li class="repo-link">
|
||||
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,9 +46,21 @@
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#show-more-repos, #show-more-recent-repos').click(function(e){
|
||||
$(e.target).parents('ul').find('li.repo-link').show();
|
||||
$(e.target).parents('li.show-more').remove();
|
||||
$('#filter-box').keyup(function(){
|
||||
var inputVal = $('#filter-box').val();
|
||||
$.each($('li.repo-link a'), function(index, elem) {
|
||||
console.log(inputVal);
|
||||
console.log(elem.text.trim());
|
||||
console.log(elem.text.trim().lastIndexOf(inputVal, 0));
|
||||
if (!inputVal || !elem.text.trim() || elem.text.trim().indexOf(inputVal) >= 0) {
|
||||
$(elem).parent().show();
|
||||
} else {
|
||||
$(elem).parent().hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
$('form.sidebar-form').submit(function () {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@(active: String = "")(implicit context: gitbucket.core.controller.Context)
|
||||
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
||||
<li @if(active == ""){ class="active"}><a href="@context.path/">News Feed</a></li>
|
||||
<li @if(active == ""){ class="active"}><a href="@context.path/">News feed</a></li>
|
||||
@if(context.loginAccount.isDefined){
|
||||
<li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull Requests</a></li>
|
||||
<li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
|
||||
<li @if(active == "issues"){ class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
|
||||
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
|
||||
@tab(context).map { link =>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
@(title: String)(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Error"){
|
||||
<h1>@title</h1>
|
||||
<div class="content-wrapper main-center">
|
||||
<div class="content body">
|
||||
<h1>@title</h1>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
@(id: String, width: Int)(implicit context: gitbucket.core.controller.Context)
|
||||
@(id: String, width: Int, user: Boolean, group: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
<span style="margin-right: 0px;">
|
||||
<input type="text" name="@id" id="@id" class="form-control" autocomplete="off" style="width: @{width}px; margin-bottom: 0px; display: inline; vertical-align: middle;"/>
|
||||
</span>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#@id').typeahead({
|
||||
// highlighter: function(item) {
|
||||
// var x = item.split(':');
|
||||
// return $('<div><strong>' + x[0] + '</strong>' + (x[1] == 'true' ? ' (group)' : '') + '</div>');
|
||||
// },
|
||||
// updater: function (item) {
|
||||
// return item.split(':')[0];
|
||||
// },
|
||||
source: function (query, process) {
|
||||
return $.get('@context.path/_user/proposals', { query: query },
|
||||
return $.get('@context.path/_user/proposals', { query: query, user: @user, group: @group },
|
||||
function (data) {
|
||||
return process(data.options);
|
||||
});
|
||||
|
||||
@@ -64,7 +64,7 @@ $(function(){
|
||||
@dropzone(clickable: Boolean, textareaId: Option[String]) = {
|
||||
url: '@context.path/upload/file/@repository.owner/@repository.name',
|
||||
maxFilesize: 10,
|
||||
clickable: false,
|
||||
clickable: @clickable,
|
||||
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
|
||||
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
|
||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree"
|
||||
) {
|
||||
<li><div id="branch-control-title">Switch branches<button id="branch-control-close" class="pull-right">×</button></div></li>
|
||||
<li><input id="branch-control-input" type="text" class="form-control input-sm" placeholder="Find or create branch ..."/></li>
|
||||
<li><input id="branch-control-input" type="text" class="form-control input-sm dropdown-filter-input" placeholder="Find or create branch ..."/></li>
|
||||
@body
|
||||
@if(hasWritePermission) {
|
||||
<li id="create-branch" style="display: none;">
|
||||
|
||||
@@ -8,20 +8,19 @@
|
||||
@if(comment.fileName.isDefined){filename="@comment.fileName.get"}
|
||||
@if(comment.newLine.isDefined){newline="@comment.newLine.get"}
|
||||
@if(comment.oldLine.isDefined){oldline="@comment.oldLine.get"}>
|
||||
<div class="issue-avatar-image">@helpers.avatarLink(comment.commentedUserName, 48)</div>
|
||||
<div class="panel panel-default commit-comment-box commit-comment-@comment.commentId">
|
||||
<div class="panel-heading">
|
||||
@helpers.avatar(comment.commentedUserName, 20)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">
|
||||
commented
|
||||
commented on
|
||||
@if(comment.issueId.isDefined){
|
||||
on this Pull Request
|
||||
} else {
|
||||
@if(comment.fileName.isDefined){
|
||||
on @comment.fileName.get
|
||||
}
|
||||
in <a href="@context.path/@repository.owner/@repository.name/commit/@comment.commitId">@comment.commitId.substring(0, 7)</a>
|
||||
<a href="@helpers.url(repository)/pull/@comment.issueId">#@comment.issueId</a>
|
||||
}
|
||||
@comment.fileName.map { fileName =>
|
||||
@fileName in
|
||||
}
|
||||
<a href="@context.path/@repository.owner/@repository.name/commit/@comment.commitId">@comment.commitId.substring(0, 7)</a>
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
</span>
|
||||
<span class="pull-right">
|
||||
|
||||
@@ -1,63 +1,48 @@
|
||||
@(id: String, value: String, style: String = "")(html: Html = Html(""))
|
||||
@(targetTextId: String, copyButtonId: String, value: String, style: String = "")(html: Html = Html(""))
|
||||
@if(html.body.nonEmpty){
|
||||
<div class="input-group" style="margin-bottom: 0px;">
|
||||
@html
|
||||
<span class="input-group-btn">
|
||||
<span id="@id" class="btn btn-default" @if(style.nonEmpty){style="@style"}
|
||||
<span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
|
||||
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
} else {
|
||||
<span id="@id" class="btn btn-default" @if(style.nonEmpty){style="@style"}
|
||||
<span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
|
||||
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
|
||||
}
|
||||
<script>
|
||||
// copy to clipboard
|
||||
(function() {
|
||||
// Check flash availablibity
|
||||
var flashAvailable = false;
|
||||
try {
|
||||
var flashObject = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
|
||||
if(flashObject) flashAvailable = true;
|
||||
} catch (e) {
|
||||
if (navigator.mimeTypes
|
||||
&& navigator.mimeTypes['application/x-shockwave-flash'] != undefined
|
||||
&& navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) {
|
||||
flashAvailable = true;
|
||||
}
|
||||
}
|
||||
// if flash is not available, remove the copy button.
|
||||
if(!flashAvailable) {
|
||||
$('#@id').remove();
|
||||
return
|
||||
}
|
||||
|
||||
// Find ZeroClipboard.swf file URI from ZeroClipboard JavaScript file path.
|
||||
// NOTE(tanacasino) I think this way is wrong... but i don't know correct way.
|
||||
var moviePath = (function() {
|
||||
var zclipjs = "ZeroClipboard.min.js";
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
var i = scripts.length;
|
||||
while(i--) {
|
||||
var match = scripts[i].src.match(zclipjs + "$");
|
||||
if(match) {
|
||||
return match.input.substr(0, match.input.length - 6) + 'swf';
|
||||
// if document.execCommand('copy') is available, use it for copy.
|
||||
if (document.queryCommandSupported('copy')) {
|
||||
var title = $('#@copyButtonId').attr('title');
|
||||
$('#@copyButtonId').tooltip({
|
||||
@* if default container is used then 2 lines tooltip text is displayd because tooptip width is narrow. *@
|
||||
container: 'body'
|
||||
});
|
||||
$('#@copyButtonId').on('click', function() {
|
||||
var target = document.getElementById('@targetTextId');
|
||||
if (!target) { @* target's id is incorrect. Fix argument's value *@
|
||||
$('#@copyButtonId').attr('title', 'failed to copy').tooltip('fixTitle').tooltip('show');
|
||||
return;
|
||||
}
|
||||
}
|
||||
})();
|
||||
var clip = new ZeroClipboard($("#@id"), {
|
||||
moviePath: moviePath
|
||||
});
|
||||
var title = $('#@id').attr('title');
|
||||
$('#@id').removeAttr('title')
|
||||
clip.htmlBridge = "#global-zeroclipboard-html-bridge";
|
||||
clip.on('complete', function(client, args) {
|
||||
$(clip.htmlBridge).attr('title', 'copied!').tooltip('fixTitle').tooltip('show');
|
||||
$(clip.htmlBridge).attr('title', title).tooltip('fixTitle');
|
||||
});
|
||||
$(clip.htmlBridge).tooltip({
|
||||
title: title,
|
||||
placement: $('#@id').attr('data-placement')
|
||||
});
|
||||
if (typeof target.select === 'function') {
|
||||
target.select();
|
||||
} else {
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(target);
|
||||
var selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
document.execCommand('copy');
|
||||
$('#@copyButtonId').attr('title', 'copied!').tooltip('fixTitle').tooltip('show');
|
||||
$('#@copyButtonId').attr('title', title).tooltip('fixTitle');
|
||||
});
|
||||
} else {
|
||||
// if copy is not supported, remove the copy button
|
||||
$('#@copyButtonId').remove();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -146,24 +146,24 @@ $(function(){
|
||||
}
|
||||
|
||||
// Render diffs as unified mode initially
|
||||
if(("&"+location.search.substring(1)).indexOf("&w=1")!=-1){
|
||||
if(("&" + location.search.substring(1)).indexOf("&w=1") != -1){
|
||||
$('.ignore-whitespace').prop('checked',true);
|
||||
}
|
||||
window.viewType=1;
|
||||
if(("&"+location.search.substring(1)).indexOf("&diff=split")!=-1){
|
||||
window.viewType = 1;
|
||||
if(("&" + location.search.substring(1)).indexOf("&diff=split") != -1){
|
||||
$('.container').removeClass('container').addClass('container-wide');
|
||||
window.viewType=0;
|
||||
window.viewType = 0;
|
||||
}
|
||||
renderDiffs();
|
||||
|
||||
$('#btn-unified').click(function(){
|
||||
window.viewType=1;
|
||||
window.viewType = 1;
|
||||
$('.container-wide').removeClass('container-wide').addClass('container');
|
||||
renderDiffs();
|
||||
});
|
||||
|
||||
$('#btn-split').click(function(){
|
||||
window.viewType=0;
|
||||
window.viewType = 0;
|
||||
$('.container').removeClass('container').addClass('container-wide');
|
||||
renderDiffs();
|
||||
});
|
||||
@@ -192,9 +192,10 @@ $(function(){
|
||||
$('#comment-list').children('.inline-comment').hide();
|
||||
}
|
||||
$('.diff-outside').on('click','table.diff .add-comment',function() {
|
||||
var $this = $(this),
|
||||
$tr = $this.closest('tr'),
|
||||
$check = $this.closest('table:not(.diff)').find('.toggle-notes');
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $check = $this.closest('table:not(.diff)').find('.toggle-notes');
|
||||
var url = '';
|
||||
if (!$check.prop('checked')) {
|
||||
$check.prop('checked', true).trigger('change');
|
||||
}
|
||||
@@ -216,35 +217,29 @@ $(function(){
|
||||
if (!isNaN(newLineNumber) && newLineNumber) {
|
||||
url += ('&newLineNumber=' + newLineNumber)
|
||||
}
|
||||
$.get(
|
||||
url,
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
function(responseContent) {
|
||||
var tmp;
|
||||
if (!isNaN(oldLineNumber) && oldLineNumber) {
|
||||
if (!isNaN(newLineNumber) && newLineNumber) {
|
||||
tmp = getInlineContainer();
|
||||
} else {
|
||||
tmp = getInlineContainer('old');
|
||||
}
|
||||
$.get(url, { dataType : 'html' }, function(responseContent) {
|
||||
var tmp;
|
||||
if (!isNaN(oldLineNumber) && oldLineNumber) {
|
||||
if (!isNaN(newLineNumber) && newLineNumber) {
|
||||
tmp = getInlineContainer();
|
||||
} else {
|
||||
tmp = getInlineContainer('new');
|
||||
tmp = getInlineContainer('old');
|
||||
}
|
||||
tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent);
|
||||
$tr.nextAll(':not(.not-diff):first').before(tmp);
|
||||
} else {
|
||||
tmp = getInlineContainer('new');
|
||||
}
|
||||
);
|
||||
tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent);
|
||||
$tr.nextAll(':not(.not-diff):first').before(tmp);
|
||||
});
|
||||
}
|
||||
}).on('click', 'table.diff .btn-default', function() {
|
||||
$(this).closest('.inline-comment-form').remove();
|
||||
});
|
||||
function renderOneCommitCommentIntoDiff($v, diff){
|
||||
var filename = $v.attr('filename'),
|
||||
oldline = $v.attr('oldline'), newline = $v.attr('newline');
|
||||
var filename = $v.attr('filename');
|
||||
var oldline = $v.attr('oldline');
|
||||
var newline = $v.attr('newline');
|
||||
var tmp;
|
||||
var diff;
|
||||
if (typeof oldline !== 'undefined') {
|
||||
if (typeof newline !== 'undefined') {
|
||||
tmp = getInlineContainer();
|
||||
@@ -252,38 +247,36 @@ $(function(){
|
||||
tmp = getInlineContainer('old');
|
||||
}
|
||||
tmp.children('td:first').html($v.clone().show());
|
||||
diff.find('table.diff').find('.oldline[line-number=' + oldline + ']')
|
||||
.parent().nextAll(':not(.not-diff):first').before(tmp);
|
||||
diff.find('table.diff').find('.oldline[line-number=' + oldline + ']').parent().nextAll(':not(.not-diff):first').before(tmp);
|
||||
} else {
|
||||
tmp = getInlineContainer('new');
|
||||
tmp.children('td:last').html($v.clone().show());
|
||||
diff.find('table.diff').find('.newline[line-number=' + newline + ']')
|
||||
.parent().nextAll(':not(.not-diff):first').before(tmp);
|
||||
diff.find('table.diff').find('.newline[line-number=' + newline + ']').parent().nextAll(':not(.not-diff):first').before(tmp);
|
||||
}
|
||||
if (!diff.find('.toggle-notes').prop('checked')) {
|
||||
tmp.hide();
|
||||
}
|
||||
}
|
||||
function renderStatBar(add,del){
|
||||
if(add+del>5){
|
||||
function renderStatBar(add, del){
|
||||
if(add + del > 5){
|
||||
if(add){
|
||||
if(add<del){
|
||||
add = Math.floor(1 + (add * 4 / (add+del)));
|
||||
}else{
|
||||
add = Math.ceil(1 + (add * 4 / (add+del)));
|
||||
if(add < del){
|
||||
add = Math.floor(1 + (add * 4 / (add + del)));
|
||||
} else {
|
||||
add = Math.ceil(1 + (add * 4 / (add + del)));
|
||||
}
|
||||
}
|
||||
del = 5-add;
|
||||
del = 5 - add;
|
||||
}
|
||||
var ret = $('<div class="diffstat-bar">');
|
||||
for(var i=0;i<5;i++){
|
||||
for(var i = 0; i < 5; i++){
|
||||
if(add){
|
||||
ret.append('<span class="text-diff-added">■</span>');
|
||||
add --;
|
||||
}else if(del){
|
||||
add--;
|
||||
} else if(del){
|
||||
ret.append('<span class="text-diff-deleted">■</span>');
|
||||
del --;
|
||||
}else{
|
||||
del--;
|
||||
} else {
|
||||
ret.append('■');
|
||||
}
|
||||
}
|
||||
@@ -294,10 +287,12 @@ $(function(){
|
||||
var i = table.data("diff-id");
|
||||
var ignoreWhiteSpace = table.find('.ignore-whitespace').prop('checked');
|
||||
diffUsingJS('oldText-'+i, 'newText-'+i, diffText.attr('id'), viewType, ignoreWhiteSpace);
|
||||
var add = diffText.find("table").attr("add")*1;
|
||||
var del = diffText.find("table").attr("del")*1;
|
||||
var add = diffText.find("table").attr("add") * 1;
|
||||
var del = diffText.find("table").attr("del") * 1;
|
||||
table.find(".diffstat").text(add+del+" ").append(renderStatBar(add,del)).attr("title",add+" additions & "+del+" deletions").tooltip();
|
||||
$('span.diffstat[data-diff-id="'+i+'"]').html('<span class="text-diff-added">+'+add+'</span><span class="text-diff-deleted">-'+del+'</span>').append(renderStatBar(add,del).attr('title',(add+del)+" lines changed").tooltip());
|
||||
$('span.diffstat[data-diff-id="'+i+'"]')
|
||||
.html('<span class="text-diff-added">+' + add + '</span><span class="text-diff-deleted">-' + del + '</span>')
|
||||
.append(renderStatBar(add, del).attr('title', (add + del) + " lines changed").tooltip());
|
||||
|
||||
@if(hasWritePermission) {
|
||||
diffText.find('.body').each(function(){ $('<b class="add-comment">+</b>').prependTo(this); });
|
||||
@@ -305,14 +300,14 @@ $(function(){
|
||||
@if(showLineNotes){
|
||||
var fileName = table.attr('filename');
|
||||
$('.inline-comment').each(function(i, v) {
|
||||
if($(this).attr('filename')==fileName){
|
||||
if($(this).attr('filename') == fileName){
|
||||
renderOneCommitCommentIntoDiff($(this), table);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function renderDiffs(){
|
||||
var i=0, diffs = $('.diffText');
|
||||
var i = 0, diffs = $('.diffText');
|
||||
function render(){
|
||||
if(diffs[i]){
|
||||
renderOneDiff($(diffs[i]), viewType);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@(value : String = "",
|
||||
prefix: String = "",
|
||||
style : String = "",
|
||||
right : Boolean = false)(body: Html)
|
||||
right : Boolean = false,
|
||||
filter: String = "")(body: Html)
|
||||
<div class="btn-group" @if(style.nonEmpty){style="@style"}>
|
||||
<button
|
||||
class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">
|
||||
@@ -16,6 +17,28 @@
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu@if(right){ pull-right}">
|
||||
@if(filter.nonEmpty) {
|
||||
<li><input id="@filter-input" type="text" class="form-control input-sm dropdown-filter-input" placeholder="Filter"/></li>
|
||||
}
|
||||
@body
|
||||
</ul>
|
||||
</div>
|
||||
@if(filter.nonEmpty) {
|
||||
<script>
|
||||
$(function(){
|
||||
$('#@{filter}-input').parent().click(function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$('#@{filter}-input').keyup(function() {
|
||||
var inputVal = $('#@{filter}-input').val();
|
||||
$.each($('#@{filter}-input').parent().parent().find('a'), function(index, elem) {
|
||||
if (!inputVal || !elem.text.trim() || elem.text.trim().lastIndexOf(inputVal, 0) >= 0) {
|
||||
$(elem).parent().show();
|
||||
} else {
|
||||
$(elem).parent().hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
@(issue: gitbucket.core.model.Issue,
|
||||
reopenable: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@if(context.loginAccount.isDefined){
|
||||
@if(isEditable){
|
||||
<hr/><br/>
|
||||
<form method="POST" validate="true">
|
||||
<div class="issue-avatar-image">@helpers.avatarLink(context.loginAccount.get.userName, 48)</div>
|
||||
<div class="panel panel-default issue-comment-box">
|
||||
<div class="panel-body">
|
||||
@gitbucket.core.helper.html.preview(
|
||||
@@ -16,7 +16,7 @@
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission,
|
||||
hasWritePermission = isEditable,
|
||||
completionContext = "issues",
|
||||
style = "",
|
||||
elastic = true,
|
||||
@@ -24,7 +24,7 @@
|
||||
)
|
||||
<div class="text-right">
|
||||
<input type="hidden" name="issueId" value="@issue.issueId"/>
|
||||
@if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == context.loginAccount.get.userName)){
|
||||
@if((reopenable || !issue.closed) && (isManageable || issue.openedUserName == context.loginAccount.get.userName)){
|
||||
<input type="submit" class="btn btn-default" tabindex="3" formaction="@helpers.url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
|
||||
}
|
||||
<input type="submit" class="btn btn-success" tabindex="2" formaction="@helpers.url(repository)/issue_comments/new" value="Comment"/>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
@(issue: Option[gitbucket.core.model.Issue],
|
||||
comments: List[gitbucket.core.model.Comment],
|
||||
hasWritePermission: Boolean,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
pullreq: Option[gitbucket.core.model.PullRequest] = None)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.model.CommitComment
|
||||
@if(issue.isDefined){
|
||||
<div class="issue-avatar-image">@helpers.avatarLink(issue.get.openedUserName, 48)</div>
|
||||
<div class="panel panel-default issue-comment-box">
|
||||
<div class="panel-heading">
|
||||
@helpers.user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate)</span>
|
||||
@helpers.avatar(issue.get.openedUserName, 20)
|
||||
@helpers.user(issue.get.openedUserName, styleClass="username strong")
|
||||
<span class="muted">commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate)</span>
|
||||
<span class="pull-right">
|
||||
@if(hasWritePermission || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
|
||||
@if(isManageable || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
|
||||
<a href="#" data-issue-id="@issue.get.issueId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
|
||||
}
|
||||
</span>
|
||||
@@ -24,7 +25,7 @@
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
hasWritePermission = isManageable
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,9 +36,9 @@
|
||||
case comment: gitbucket.core.model.IssueComment => {
|
||||
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"
|
||||
&& comment.action != "commit" && comment.action != "refer"){
|
||||
<div class="issue-avatar-image">@helpers.avatarLink(comment.commentedUserName, 48)</div>
|
||||
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
|
||||
<div class="panel-heading">
|
||||
@helpers.avatar(comment.commentedUserName, 20)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">
|
||||
@if(comment.action == "comment"){
|
||||
@@ -48,7 +49,7 @@
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
</span>
|
||||
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
|
||||
&& (hasWritePermission || context.loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
||||
&& (isManageable || context.loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
||||
<span class="pull-right">
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a>
|
||||
@@ -63,7 +64,7 @@
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
hasWritePermission = isManageable
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,7 +167,7 @@
|
||||
}
|
||||
}
|
||||
case comment: CommitComment => {
|
||||
@gitbucket.core.helper.html.commitcomment(comment, hasWritePermission, repository, pullreq.map(_.commitIdTo))
|
||||
@gitbucket.core.helper.html.commitcomment(comment, isManageable, repository, pullreq.map(_.commitIdTo))
|
||||
}
|
||||
}
|
||||
<script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@(collaborators: List[String],
|
||||
milestones: List[gitbucket.core.model.Milestone],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
hasWritePermission: Boolean,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@@ -18,7 +18,7 @@
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission,
|
||||
hasWritePermission = isManageable,
|
||||
completionContext = "issues",
|
||||
style = "height: 200px; max-height: 250px;",
|
||||
elastic = true
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, hasWritePermission, repository)
|
||||
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, isManageable, repository)
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -4,17 +4,20 @@
|
||||
collaborators: List[String],
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
hasWritePermission: Boolean,
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"${issue.title} - Issue #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("issues", repository){
|
||||
<div>
|
||||
<div class="show-title pull-right">
|
||||
@if(hasWritePermission || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
@if(isManageable || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
<a class="btn btn-default" href="#" id="edit">Edit</a>
|
||||
}
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/issues/new">New issue</a>
|
||||
@if(isEditable){
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/issues/new">New issue</a>
|
||||
}
|
||||
</div>
|
||||
<div class="edit-title pull-right" style="display: none;">
|
||||
<a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
|
||||
@@ -47,11 +50,11 @@
|
||||
<hr>
|
||||
<div style="margin-top: 15px;">
|
||||
<div class="col-md-9">
|
||||
@gitbucket.core.issues.html.commentlist(Some(issue), comments, hasWritePermission, repository)
|
||||
@gitbucket.core.issues.html.commentform(issue, true, hasWritePermission, repository)
|
||||
@gitbucket.core.issues.html.commentlist(Some(issue), comments, isManageable, repository)
|
||||
@gitbucket.core.issues.html.commentform(issue, true, isEditable, isManageable, repository)
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, isManageable, repository)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
collaborators: List[String],
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
hasWritePermission: Boolean,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
<div style="margin-bottom: 14px;">
|
||||
<span class="muted small strong">Labels</span>
|
||||
@if(hasWritePermission){
|
||||
@if(isManageable){
|
||||
<div class="pull-right">
|
||||
@gitbucket.core.helper.html.dropdown("Edit", right = true) {
|
||||
@gitbucket.core.helper.html.dropdown("Edit", right = true, filter = "labels") {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="#" class="toggle-label" data-label-id="@label.labelId">
|
||||
@@ -34,9 +34,9 @@
|
||||
<hr/>
|
||||
<div style="margin-bottom: 14px;">
|
||||
<span class="muted small strong">Milestone</span>
|
||||
@if(hasWritePermission){
|
||||
@if(isManageable){
|
||||
<div class="pull-right">
|
||||
@gitbucket.core.helper.html.dropdown("Edit", right = true) {
|
||||
@gitbucket.core.helper.html.dropdown("Edit", right = true, filter = "milestone") {
|
||||
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="octicon octicon-x"></i> Clear this milestone</a></li>
|
||||
@milestones.filter(_._1.closedDate.isEmpty).map { case (milestone, _, _) =>
|
||||
<li>
|
||||
@@ -74,7 +74,7 @@
|
||||
<span id="label-milestone">
|
||||
@issue.flatMap(_.milestoneId).map { milestoneId =>
|
||||
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
|
||||
<span class="strong small">@milestone.title</span>
|
||||
<a class="strong small username" href="@helpers.url(repository)/issues?milestone=@helpers.urlEncode(milestone.title)&state=open">@milestone.title</a>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted small">No milestone</span>
|
||||
@@ -86,9 +86,9 @@
|
||||
<hr/>
|
||||
<div style="margin-bottom: 14px;">
|
||||
<span class="muted small strong">Assignee</span>
|
||||
@if(hasWritePermission){
|
||||
@if(isManageable){
|
||||
<div class="pull-right">
|
||||
@gitbucket.core.helper.html.dropdown("Edit", right = true) {
|
||||
@gitbucket.core.helper.html.dropdown("Edit", right = true, filter = "assignee") {
|
||||
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
@@ -210,7 +210,8 @@ $(function(){
|
||||
$('#label-milestone').html($('<span class="muted small">').text('No milestone'));
|
||||
$('#milestone-progress-area').empty();
|
||||
} else {
|
||||
$('#label-milestone').html($('<span class="strong small">').text(title));
|
||||
$('#label-milestone').html($('<a class="strong small username">').text(title)
|
||||
.attr('href', '@helpers.url(repository)/issues?milestone=' + encodeURIComponent(title) + '&state=open'));
|
||||
if(progress){
|
||||
$('#milestone-progress-area').html(progress);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
closedCount: Int,
|
||||
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu(target, repository){
|
||||
@@ -20,18 +21,25 @@
|
||||
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
<form method="GET" id="search-filter-form" class="form-inline pull-right">
|
||||
@if(context.loginAccount.isDefined){
|
||||
@if(target == "issues"){
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/issues/new">New issue</a>
|
||||
<form method="GET" action="@helpers.url(repository)/search" id="search-filter-form" class="form-inline pull-right">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="q" placeholder="Search..."/>
|
||||
<input type="hidden" name="type" value="issue"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" id="search-btn" class="btn btn-default"><i class="fa fa-search"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
@if(isEditable){
|
||||
@if(target == "issues"){
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/issues/new">New issue</a>
|
||||
}
|
||||
@if(target == "pulls"){
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
|
||||
}
|
||||
}
|
||||
@if(target == "pulls"){
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
|
||||
}
|
||||
}
|
||||
</form>
|
||||
@gitbucket.core.issues.html.listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission)
|
||||
@if(hasWritePermission){
|
||||
@gitbucket.core.issues.html.listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), isManageable)
|
||||
@if(isManageable){
|
||||
<form id="batcheditForm" method="POST">
|
||||
<input type="hidden" name="value"/>
|
||||
<input type="hidden" name="checked"/>
|
||||
@@ -40,7 +48,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@if(hasWritePermission){
|
||||
@if(isManageable){
|
||||
<script>
|
||||
$(function(){
|
||||
$('a.header-link').mouseover(function(e){
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
milestones: List[gitbucket.core.model.Milestone] = Nil,
|
||||
labels: List[gitbucket.core.model.Label] = Nil,
|
||||
repository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo] = None,
|
||||
hasWritePermission: Boolean = false)(implicit context: gitbucket.core.controller.Context)
|
||||
isManageable: Boolean = false)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.service.IssuesService.IssueInfo
|
||||
@*
|
||||
@@ -110,7 +110,7 @@
|
||||
</li>
|
||||
}
|
||||
</span>
|
||||
@if(hasWritePermission){
|
||||
@if(isManageable){
|
||||
<span id="table-issues-batchedit">
|
||||
@gitbucket.core.helper.html.dropdown("Mark as") {
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="open">Open</a></li>
|
||||
@@ -174,7 +174,7 @@
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
|
||||
<tr>
|
||||
<td style="padding-top: 12px; padding-bottom: 12px;">
|
||||
@if(hasWritePermission){
|
||||
@if(isManageable){
|
||||
<input type="checkbox" value="@issue.issueId"/>
|
||||
}
|
||||
@*
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
<link href="@helpers.assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
|
||||
<link href="@helpers.assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
|
||||
<link href="@helpers.assets/vendors/facebox/facebox.css" rel="stylesheet"/>
|
||||
<link href="@helpers.assets/vendors/AdminLTE-2.3.6/css/AdminLTE.min.css" rel="stylesheet">
|
||||
<link href="@helpers.assets/vendors/AdminLTE-2.3.6/css/skins/skin-blue.min.css" rel="stylesheet">
|
||||
<link href="@helpers.assets/vendors/AdminLTE-2.3.8/css/AdminLTE.min.css" rel="stylesheet">
|
||||
<link href="@helpers.assets/vendors/AdminLTE-2.3.8/css/skins/skin-blue.min.css" rel="stylesheet">
|
||||
<link href="@helpers.assets/vendors/font-awesome-4.6.3/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="@helpers.assets/common/css/gitbucket.css" rel="stylesheet">
|
||||
<script src="@helpers.assets/vendors/jquery/jquery-1.11.1.js"></script>
|
||||
<script src="@helpers.assets/vendors/jquery/jquery-1.12.2.min.js"></script>
|
||||
<script src="@helpers.assets/vendors/dropzone/dropzone.js"></script>
|
||||
<script src="@helpers.assets/common/js/validation.js"></script>
|
||||
<script src="@helpers.assets/common/js/gitbucket.js"></script>
|
||||
@@ -29,7 +29,6 @@
|
||||
<script src="@helpers.assets/vendors/datepicker/js/bootstrap-datetimepicker.min.js"></script>
|
||||
<script src="@helpers.assets/vendors/colorpicker/js/bootstrap-colorpicker.js"></script>
|
||||
<script src="@helpers.assets/vendors/google-code-prettify/prettify.js"></script>
|
||||
<script src="@helpers.assets/vendors/zclip/ZeroClipboard.min.js"></script>
|
||||
<script src="@helpers.assets/vendors/elastic/jquery.elastic.source.js"></script>
|
||||
<script src="@helpers.assets/vendors/facebox/facebox.js"></script>
|
||||
<script src="@helpers.assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script>
|
||||
@@ -37,9 +36,9 @@
|
||||
@repository.map { repository =>
|
||||
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
|
||||
}
|
||||
<script src="@helpers.assets/vendors/AdminLTE-2.3.6/js/app.js" type="text/javascript"></script>
|
||||
<script src="@helpers.assets/vendors/AdminLTE-2.3.8/js/app.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body class="skin-blue page-load">
|
||||
<body class="skin-blue page-load @if(context.sidebarCollapse){sidebar-collapse}">
|
||||
<div class="wrapper">
|
||||
<header class="main-header">
|
||||
<a href="@context.path/" class="logo">
|
||||
@@ -54,15 +53,11 @@
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
</a>
|
||||
}
|
||||
@repository.map { repository =>
|
||||
<form id="search" action="@context.path/search" method="POST" class="pc navbar-form navbar-left" role="search">
|
||||
<div class="form-group">
|
||||
<input type="text" name="query" id="navbar-search-input" class="form-control" placeholder="Search this repository"/>
|
||||
<input type="hidden" name="owner" value="@repository.owner"/>
|
||||
<input type="hidden" name="repository" value="@repository.name"/>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
<form id="search" action="@context.path/search" method="GET" class="pc navbar-form navbar-left" role="search">
|
||||
<div class="form-group">
|
||||
<input type="text" name="query" id="navbar-search-input" class="form-control" placeholder="Search repository"/>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="pc nav navbar-nav">
|
||||
@if(context.loginAccount.isDefined){
|
||||
<li><a href="@context.path/dashboard/pulls">Pull requests</a></li>
|
||||
@@ -115,6 +110,9 @@
|
||||
$('#search').submit(function(){
|
||||
return $.trim($(this).find('input[name=query]').val()) != '';
|
||||
});
|
||||
$(".sidebar-toggle").on('click', function(e){
|
||||
$.get('@context.path/sidebar-collapse', { collapse: !$('body').hasClass('sidebar-collapse') });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@PluginRegistry().getJavaScript(context.request.getRequestURI).map { script =>
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
<div class="sidebar">
|
||||
<ul class="sidebar-menu">
|
||||
@menuitem("", "files", "Files", "code")
|
||||
@if(repository.commitCount != 0) {
|
||||
@if(repository.branchList.nonEmpty) {
|
||||
@menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length)
|
||||
@menuitem("/tags", "tags", "Tags", "tag", repository.tags.length)
|
||||
}
|
||||
@if(repository.repository.options.enableIssues) {
|
||||
@if(repository.repository.options.issuesOption != "DISABLE") {
|
||||
@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/milestones", "milestones", "Milestones", "milestone")
|
||||
} else {
|
||||
@@ -37,7 +37,7 @@
|
||||
@menuitem(externalIssuesUrl, "issues", "Issues", "issue-opened")
|
||||
}
|
||||
}
|
||||
@if(repository.repository.options.enableWiki) {
|
||||
@if(repository.repository.options.wikiOption != "DISABLE") {
|
||||
@menuitem("/wiki", "wiki", "Wiki", "book")
|
||||
} else {
|
||||
@repository.repository.options.externalWikiUrl.map { externalWikiUrl =>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
milestones: List[gitbucket.core.model.Milestone],
|
||||
labels: List[gitbucket.core.model.Label])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"Pull Requests - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.main(s"Pull requests - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("pulls", repository){
|
||||
<div class="pullreq-info">
|
||||
<div id="compare-edit">
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
collaborators: List[String],
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
hasWritePermission: Boolean,
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
<div class="col-md-9">
|
||||
<div id="comment-list">
|
||||
@gitbucket.core.issues.html.commentlist(Some(issue), comments, hasWritePermission, repository, Some(pullreq))
|
||||
@gitbucket.core.issues.html.commentlist(Some(issue), comments, isManageable, repository, Some(pullreq))
|
||||
</div>
|
||||
@defining(comments.flatMap {
|
||||
case comment: gitbucket.core.model.IssueComment => Some(comment)
|
||||
@@ -25,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if(hasWritePermission && issue.closed && pullreq.userName == pullreq.requestUserName && merged &&
|
||||
@if(isManageable && issue.closed && pullreq.userName == pullreq.requestUserName && merged &&
|
||||
pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){
|
||||
<div class="issue-comment-box" style="background-color: #d0eeff;">
|
||||
<div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;">
|
||||
@@ -37,11 +38,11 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@gitbucket.core.issues.html.commentform(issue, !merged, hasWritePermission, repository)
|
||||
@gitbucket.core.issues.html.commentform(issue, !merged, isEditable, isManageable, repository)
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, isManageable, repository)
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
@@ -55,7 +56,7 @@ $(function(){
|
||||
$.get('@helpers.url(repository)/pull/@issue.issueId/mergeguide', function(data){ $('.check-conflict').html(data); });
|
||||
}
|
||||
|
||||
@if(hasWritePermission){
|
||||
@if(isManageable){
|
||||
$('.delete-branch').click(function(e){
|
||||
var branchName = $(e.target).data('name');
|
||||
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
you can perform a manual merge on the command line.
|
||||
</p>
|
||||
}
|
||||
@gitbucket.core.helper.html.copy("repository-url-copy", forkedRepository.httpUrl){
|
||||
@gitbucket.core.helper.html.copy("repository-url", "repository-url-copy", forkedRepository.httpUrl){
|
||||
<div class="input-group-btn" data-toggle="buttons">
|
||||
<label class="btn btn-sm btn-default active" id="repository-url-http"><input type="radio" checked>HTTP</label>
|
||||
@if(context.settings.ssh && context.loginAccount.isDefined){
|
||||
@@ -114,7 +114,7 @@
|
||||
</p>
|
||||
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
|
||||
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
|
||||
@gitbucket.core.helper.html.copy("merge-command-copy-1", command, "position: absolute; right: 31px;")()
|
||||
@gitbucket.core.helper.html.copy("merge-command", "merge-command-copy-1", command, "position: absolute; right: 31px;")()
|
||||
<pre style="font-size: 12px; border-radius: 3px;" id="merge-command">@Html(command)</pre>
|
||||
}
|
||||
</div>
|
||||
@@ -124,8 +124,8 @@
|
||||
</p>
|
||||
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
|
||||
s"git push origin ${pullreq.branch}"){ command =>
|
||||
@gitbucket.core.helper.html.copy("merge-command-copy-2", command, "position: absolute; right: 31px;")()
|
||||
<pre style="font-size: 12px; border-radius: 3px;">@command</pre>
|
||||
@gitbucket.core.helper.html.copy("merge-command-2", "merge-command-copy-2", command, "position: absolute; right: 31px;")()
|
||||
<pre style="font-size: 12px; border-radius: 3px;" id="merge-command-2">@command</pre>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,4 +196,4 @@ $(function(){
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -7,18 +7,19 @@
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
||||
hasWritePermission: Boolean,
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
flash: Map[String, String])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.model.IssueComment
|
||||
@import gitbucket.core.model.CommitComment
|
||||
@gitbucket.core.html.main(s"${issue.title} - Pull Request #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.main(s"${issue.title} - Pull request #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("pulls", repository){
|
||||
@defining(dayByDayCommits.flatten){ commits =>
|
||||
<div>
|
||||
<div class="show-title pull-right">
|
||||
@if(hasWritePermission || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
@if(isManageable || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
<a class="btn" href="#" id="edit">Edit</a>
|
||||
}
|
||||
@if(context.loginAccount.isDefined){
|
||||
@@ -82,13 +83,13 @@
|
||||
@flash.get("info").map{ info =>
|
||||
<div class="alert alert-info">@info</div>
|
||||
}
|
||||
@gitbucket.core.pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
@gitbucket.core.pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, isEditable, isManageable, repository)
|
||||
</div>
|
||||
<div class="tab-pane" id="commits">
|
||||
@gitbucket.core.pulls.html.commits(dayByDayCommits, Some(comments), repository)
|
||||
</div>
|
||||
<div class="tab-pane" id="files">
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, Some(commits.head.id), Some(commits.last.id), true, Some(pullreq.issueId), hasWritePermission, true)
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, Some(commits.head.id), Some(commits.last.id), true, Some(pullreq.issueId), isManageable, true)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -98,13 +99,13 @@
|
||||
$(function(){
|
||||
// Determine active tab from hash
|
||||
if(location.hash == '#commits'){
|
||||
$('li:has(a[href=#commits])').addClass('active');
|
||||
$('li:has(a[href="#commits"])').addClass('active');
|
||||
$('div#commits').addClass('active');
|
||||
} else if(location.hash == '#files'){
|
||||
$('li:has(a[href=#files])').addClass('active');
|
||||
$('li:has(a[href="#files"])').addClass('active');
|
||||
$('div#files').addClass('active');
|
||||
} else {
|
||||
$('li:has(a[href=#conversation])').addClass('active');
|
||||
$('li:has(a[href="#conversation"])').addClass('active');
|
||||
$('div#conversation').addClass('active');
|
||||
}
|
||||
// Set hash when tab is clicked
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
helpers.urlEncode(parent) + ":" + helpers.encodeRefName(repository.repository.defaultBranch)
|
||||
}.getOrElse {
|
||||
helpers.encodeRefName(repository.repository.defaultBranch)
|
||||
}}...@{helpers.encodeRefName(branch.name)}?expand=1" class="btn btn-default">New Pull Request</a>
|
||||
}}...@{helpers.encodeRefName(branch.name)}?expand=1" class="btn btn-default">New Pull request</a>
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/compare/@{repository.repository.parentUserName.map { parent =>
|
||||
helpers.urlEncode(parent) + ":" + helpers.encodeRefName(repository.repository.defaultBranch)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
pathList: List[String],
|
||||
latestCommit: gitbucket.core.util.JGitUtil.CommitInfo,
|
||||
commitCount: Int,
|
||||
files: List[gitbucket.core.util.JGitUtil.FileInfo],
|
||||
readme: Option[(List[String], String)],
|
||||
hasWritePermission: Boolean,
|
||||
@@ -25,7 +26,7 @@
|
||||
<div class="pull-right">
|
||||
<div class="btn-group">
|
||||
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t"><i class="octicon octicon-search"></i></a>
|
||||
<a href="@helpers.url(repository)/commits/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i> @if(repository.commitCount > 10000){10000+} else {@repository.commitCount} @helpers.plural(repository.commitCount, "commit")</a>
|
||||
<a href="@helpers.url(repository)/commits/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i> @if(commitCount > 10000){10000+} else {@commitCount} @helpers.plural(commitCount, "commit")</a>
|
||||
</div>
|
||||
</div>
|
||||
@if(pathList.isEmpty){
|
||||
@@ -39,7 +40,7 @@
|
||||
</div>
|
||||
<div class="pull-right pc">
|
||||
<div style="width: 240px; margin-right: 5px; margin-left: 5px;">
|
||||
@gitbucket.core.helper.html.copy("repository-url-copy", repository.httpUrl){
|
||||
@gitbucket.core.helper.html.copy("repository-url", "repository-url-copy", repository.httpUrl){
|
||||
@if(repository.sshUrl.isDefined){
|
||||
<div class="btn-group input-group-btn">
|
||||
<button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@@ -85,7 +86,9 @@
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
||||
}
|
||||
}
|
||||
<a href="@helpers.url(repository)/new/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here" @if(!hasWritePermission){disabled}><i class="octicon octicon-plus"></i></a>
|
||||
@if(hasWritePermission){
|
||||
<a href="@helpers.url(repository)/new/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here"><i class="octicon octicon-plus"></i></a>
|
||||
}
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@*
|
||||
@@ -143,7 +146,7 @@
|
||||
<i class="octicon octicon-file-text"></i>
|
||||
}
|
||||
</td>
|
||||
<td class="content-name">
|
||||
<td class="ellipsis-cell" style="width: 20%; min-width: 160px;">
|
||||
@if(file.isDirectory){
|
||||
@if(file.linkUrl.isDefined){
|
||||
<a href="@file.linkUrl">
|
||||
@@ -164,10 +167,10 @@
|
||||
<a href="@helpers.url(repository)/blob@{(branch :: pathList).map(helpers.encodeRefName).mkString("/", "/", "/")}@{helpers.encodeRefName(file.name)}">@file.name</a>
|
||||
}
|
||||
</td>
|
||||
<td class="mute">
|
||||
<a href="@helpers.url(repository)/commit/@file.commitId" class="commit-message shorten-text" title="@file.message">@helpers.link(file.message, repository)</a>
|
||||
<td class="ellipsis-cell" style="width: 70%;">
|
||||
<a href="@helpers.url(repository)/commit/@file.commitId" class="commit-message" title="@file.message">@helpers.link(file.message, repository)</a>
|
||||
</td>
|
||||
<td style="text-align: right;">@gitbucket.core.helper.html.datetimeago(file.time, false)</td>
|
||||
<td style="width: 10%; min-width: 125px; text-align: right;">@gitbucket.core.helper.html.datetimeago(file.time, false)</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
@(files: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
|
||||
issueCount: Int,
|
||||
wikiCount: Int,
|
||||
query: String,
|
||||
page: Int,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.service.RepositorySearchService
|
||||
@gitbucket.core.html.main("Search Results", Some(repository)){
|
||||
@gitbucket.core.search.html.menu("code", files.size, issueCount, wikiCount, query, repository){
|
||||
@if(files.isEmpty){
|
||||
<h4>We couldn't find any code matching '@query'</h4>
|
||||
} else {
|
||||
<h4>We've found @files.size code @helpers.plural(files.size, "result")</h4>
|
||||
@gitbucket.core.search.html.menu("files", query, repository){
|
||||
@if(query.nonEmpty) {
|
||||
@if(files.isEmpty) {
|
||||
<h4>We couldn't find any code matching '@query'</h4>
|
||||
} else {
|
||||
<h4>We've found @files.size code @helpers.plural(files.size, "result")</h4>
|
||||
}
|
||||
}
|
||||
@files.drop((page - 1) * RepositorySearchService.CodeLimit).take(RepositorySearchService.CodeLimit).map { file =>
|
||||
<div>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
@(fileCount: Int,
|
||||
issues: List[gitbucket.core.service.RepositorySearchService.IssueSearchResult],
|
||||
wikiCount: Int,
|
||||
@(issues: List[gitbucket.core.service.RepositorySearchService.IssueSearchResult],
|
||||
query: String,
|
||||
page: Int,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.service.RepositorySearchService
|
||||
@gitbucket.core.html.main("Search Results", Some(repository)){
|
||||
@gitbucket.core.search.html.menu("issue", fileCount, issues.size, wikiCount, query, repository){
|
||||
@if(issues.isEmpty){
|
||||
<h4>We couldn't find any code matching '@query'</h4>
|
||||
} else {
|
||||
<h4>We've found @issues.size code @helpers.plural(issues.size, "result")</h4>
|
||||
@gitbucket.core.search.html.menu("issues", query, repository){
|
||||
@if(query.nonEmpty) {
|
||||
@if(issues.isEmpty) {
|
||||
<h4>We couldn't find any code matching '@query'</h4>
|
||||
} else {
|
||||
<h4>We've found @issues.size @helpers.plural(issues.size, "issue")</h4>
|
||||
}
|
||||
}
|
||||
@issues.drop((page - 1) * RepositorySearchService.IssueLimit).take(RepositorySearchService.IssueLimit).map { issue =>
|
||||
<div class="block">
|
||||
|
||||
@@ -1,35 +1,18 @@
|
||||
@(active: String, fileCount: Int, issueCount: Int, wikiCount: Int, query: String,
|
||||
@(active: String, query: String,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.menu("", repository){
|
||||
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
||||
<li@if(active=="code"){ class="active"}>
|
||||
<a href="@helpers.url(repository)/search?q=@helpers.urlEncode(query)&type=code">
|
||||
Files
|
||||
@if(fileCount != 0){
|
||||
<span class="badge">@fileCount</span>
|
||||
}
|
||||
</a>
|
||||
</li>
|
||||
<li@if(active=="issue"){ class="active"}>
|
||||
<a href="@helpers.url(repository)/search?q=@helpers.urlEncode(query)&type=issue">
|
||||
Issues
|
||||
@if(issueCount != 0){
|
||||
<span class="badge">@issueCount</span>
|
||||
}
|
||||
</a>
|
||||
</li>
|
||||
<li@if(active=="wiki"){ class="active"}>
|
||||
<a href="@helpers.url(repository)/search?q=@helpers.urlEncode(query)&type=wiki">
|
||||
Wiki
|
||||
@if(wikiCount != 0){
|
||||
<span class="badge">@wikiCount</span>
|
||||
}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@gitbucket.core.html.menu(active, repository){
|
||||
<form action="@helpers.url(repository)/search" method="GET" class="form-inline">
|
||||
<input type="text" name="q" value="@query" class="form-control" style="width: 400px; margin-bottom: 0px;"/>
|
||||
<select class="form-control" name="type">
|
||||
<option value="code" @if(active == "files"){ selected }>Files</option>
|
||||
@if(repository.repository.options.issuesOption != "DISABLE") {
|
||||
<option value="issue" @if(active == "issues"){ selected }>Issues</option>
|
||||
}
|
||||
@if(repository.repository.options.wikiOption != "DISABLE") {
|
||||
<option value="wiki" @if(active == "wiki"){ selected }>Wiki</option>
|
||||
}
|
||||
</select>
|
||||
<input type="text" name="q" value="@query" class="form-control" style="width: 250px;" placeholder="Search..."/>
|
||||
<input type="submit" value="Search" class="btn btn-default"/>
|
||||
<input type="hidden" name="type" value="@active"/>
|
||||
</form>
|
||||
|
||||
40
src/main/twirl/gitbucket/core/search/repositories.scala.html
Normal file
40
src/main/twirl/gitbucket/core/search/repositories.scala.html
Normal file
@@ -0,0 +1,40 @@
|
||||
@(query: String,
|
||||
repositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main("GitBucket"){
|
||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){
|
||||
<form action="@context.path/search" method="GET" class="form-inline">
|
||||
<input type="text" name="query" value="@query" class="form-control" style="width: 250px; margin-bottom: 0px;"/>
|
||||
<input type="submit" value="Search" class="btn btn-default"/>
|
||||
</form>
|
||||
@if(repositories.isEmpty) {
|
||||
<h4>We couldn't find any repository matching '@query'</h4>
|
||||
} else {
|
||||
<h4>We've found @repositories.size @helpers.plural(repositories.size, "repository", "repositories")</h4>
|
||||
}
|
||||
@repositories.map { repository =>
|
||||
<div class="block">
|
||||
<div class="repository-icon">
|
||||
@gitbucket.core.helper.html.repositoryicon(repository, true)
|
||||
</div>
|
||||
<div class="repository-content">
|
||||
<div class="block-header">
|
||||
<a href="@helpers.url(repository)">@repository.owner/@repository.name</a>
|
||||
@if(repository.repository.isPrivate){
|
||||
<i class="octicon octicon-lock"></i>
|
||||
}
|
||||
</div>
|
||||
@if(repository.repository.originUserName.isDefined){
|
||||
<div class="small muted">forked from <a href="@context.path/@repository.repository.parentUserName/@repository.repository.parentRepositoryName">@repository.repository.parentUserName/@repository.repository.parentRepositoryName</a></div>
|
||||
}
|
||||
@if(repository.repository.description.isDefined){
|
||||
<div>@repository.repository.description</div>
|
||||
}
|
||||
<div><span class="muted small">Updated @gitbucket.core.helper.html.datetimeago(repository.repository.lastActivityDate)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
@(fileCount: Int,
|
||||
issueCount: Int,
|
||||
wikis: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
|
||||
@(wikis: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
|
||||
query: String,
|
||||
page: Int,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.service.RepositorySearchService
|
||||
@gitbucket.core.html.main("Search Results", Some(repository)){
|
||||
@gitbucket.core.search.html.menu("wiki", fileCount, issueCount, wikis.size, query, repository){
|
||||
@if(wikis.isEmpty){
|
||||
<h4>We couldn't find any code matching '@query'</h4>
|
||||
} else {
|
||||
<h4>We've found @wikis.size code @helpers.plural(wikis.size, "result")</h4>
|
||||
@gitbucket.core.search.html.menu("wiki", query, repository){
|
||||
@if(query.nonEmpty) {
|
||||
@if(wikis.isEmpty) {
|
||||
<h4>We couldn't find any code matching '@query'</h4>
|
||||
} else {
|
||||
<h4>We've found @wikis.size @helpers.plural(wikis.size, "page")</h4>
|
||||
}
|
||||
}
|
||||
@wikis.drop((page - 1) * RepositorySearchService.CodeLimit).take(RepositorySearchService.CodeLimit).map { file =>
|
||||
<div>
|
||||
|
||||
@@ -1,34 +1,140 @@
|
||||
@(collaborators: List[String],
|
||||
@(collaborators: List[(gitbucket.core.model.Collaborator, Boolean)],
|
||||
isGroupRepository: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.model.Role
|
||||
@gitbucket.core.html.main("Settings", Some(repository)){
|
||||
@gitbucket.core.html.menu("settings", repository){
|
||||
@gitbucket.core.settings.html.menu("collaborators", repository){
|
||||
<h3>Manage Collaborators</h3>
|
||||
<ul class="collaborator">
|
||||
@collaborators.map { collaboratorName =>
|
||||
<li>
|
||||
<a href="@helpers.url(collaboratorName)">@collaboratorName</a>
|
||||
@if(!isGroupRepository){
|
||||
<a href="@helpers.url(repository)/settings/collaborators/remove?name=@collaboratorName" class="remove">(remove)</a>
|
||||
} else {
|
||||
@if(repository.managers.contains(collaboratorName)){
|
||||
(Manager)
|
||||
}
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@if(!isGroupRepository){
|
||||
<form method="POST" action="@helpers.url(repository)/settings/collaborators/add" validate="true" autocomplete="off">
|
||||
<div>
|
||||
<span class="error" id="error-userName"></span>
|
||||
<form id="form" method="post" action="@helpers.url(repository)/settings/collaborators">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Collaborators</div>
|
||||
<div class="panel-body">
|
||||
<ul id="collaborator-list" class="collaborator">
|
||||
</ul>
|
||||
@gitbucket.core.helper.html.account("userName-collaborator", 200, true, false)
|
||||
<input type="button" class="btn btn-default add" value="Add" id="addCollaborator"/>
|
||||
<div>
|
||||
<span class="error" id="error-collaborator"></span>
|
||||
</div>
|
||||
</div>
|
||||
@gitbucket.core.helper.html.account("userName", 300)
|
||||
<input type="submit" class="btn btn-default" value="Add"/>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Groups</div>
|
||||
<div class="panel-body">
|
||||
<ul id="group-list" class="collaborator">
|
||||
</ul>
|
||||
@gitbucket.core.helper.html.account("userName-group", 200, false, true)
|
||||
<input type="button" class="btn btn-default add" value="Add" id="addGroup"/>
|
||||
<div>
|
||||
<span class="error" id="error-group"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="align-right" style="margin-top: 20px;">
|
||||
<input type="hidden" id="collaborators" name="collaborators" />
|
||||
<input type="submit" class="btn btn-success" value="Apply changes"/>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('input[type=submit]').click(function(){
|
||||
updateValues();
|
||||
});
|
||||
|
||||
$('.add').click(function(){
|
||||
var id = $(this).attr('id') == 'addCollaborator' ? 'collaborator' : 'group';
|
||||
|
||||
$('#error-' + id).text('');
|
||||
var userName = $('#userName-' + id).val();
|
||||
|
||||
// check empty
|
||||
if($.trim(userName) == ''){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check owner
|
||||
var owner = '@repository.owner' == userName
|
||||
if(owner){
|
||||
$('#error-' + id).text('User is owner of this repository.');
|
||||
return false;
|
||||
}
|
||||
// check duplication
|
||||
var exists = $('#' + id + '-list li').filter(function(){
|
||||
return $(this).data('name') == userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-' + id).text('User has been already added.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', { 'userName': userName },
|
||||
function(data, status){
|
||||
if(data != ''){
|
||||
addListHTML(userName, '@Role.ADMIN.name', '#' + id + '-list');
|
||||
$('#userName-' + id).val('');
|
||||
} else {
|
||||
$('#error-' + id).text('User does not exist.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.remove', function(){
|
||||
$(this).parent().remove();
|
||||
});
|
||||
|
||||
// Don't submit form by ENTER key
|
||||
$('#userName-collaborator, #userName-group').keypress(function(e){
|
||||
return !(e.keyCode == 13);
|
||||
});
|
||||
|
||||
@collaborators.map { case (collaborator, isGroup) =>
|
||||
addListHTML('@collaborator.collaboratorName', '@collaborator.role', @if(isGroup){'#group-list'}else{'#collaborator-list'});
|
||||
}
|
||||
|
||||
function addListHTML(userName, role, id){
|
||||
var adminButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.ADMIN.name" name="' + userName + '">Admin</label>');
|
||||
if(role == '@Role.ADMIN.name'){
|
||||
adminButton.addClass('active');
|
||||
}
|
||||
var writeButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.DEVELOPER.name" name="' + userName + '">Developer</label>');
|
||||
if(role == '@Role.DEVELOPER.name'){
|
||||
writeButton.addClass('active');
|
||||
}
|
||||
var readButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.GUEST.name" name="' + userName + '">Guest</label>');
|
||||
if(role == '@Role.GUEST.name'){
|
||||
readButton.addClass('active');
|
||||
}
|
||||
|
||||
$(id).append($('<li>')
|
||||
.data('name', userName)
|
||||
.append($('<div class="btn-group role" data-toggle="buttons">')
|
||||
.append(adminButton)
|
||||
.append(writeButton)
|
||||
.append(readButton))
|
||||
.append(' ')
|
||||
.append($('<a target="_blank">').attr('href', '@context.path/' + userName).text(userName))
|
||||
.append($('<a href="#" class="remove pull-right">(remove)</a>')));
|
||||
}
|
||||
|
||||
function updateValues(){
|
||||
var collaborators = $('#collaborator-list li').map(function(i, e){
|
||||
var userName = $(e).data('name');
|
||||
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
|
||||
}).get().join(',');
|
||||
|
||||
var groups = $('#group-list li').map(function(i, e){
|
||||
var userName = $(e).data('name');
|
||||
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
|
||||
}).get().join(',');
|
||||
|
||||
$('#collaborators').val(collaborators + ',' + groups);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -13,7 +13,7 @@
|
||||
<div>
|
||||
Transfer this repo to another user or to group.
|
||||
<div class="pull-right">
|
||||
@gitbucket.core.helper.html.account("newOwner", 200)
|
||||
@gitbucket.core.helper.html.account("newOwner", 200, true, true)
|
||||
<input type="submit" class="btn btn-danger" value="Transfer"/>
|
||||
<div>
|
||||
<span id="error-newOwner" class="error"></span>
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
-->
|
||||
<label class="checkbox"><input type="checkbox" @check("events",IssueComment) />Issue comment <span class="help-block normal">Issue commented on. </span> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",Issues) />Issues <span class="help-block normal">Issue opened, closed<!-- , assigned, or labeled -->. </span> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",PullRequest) />Pull Request <span class="help-block normal">Pull Request opened, closed<!-- , assigned, labeled -->, or synchronized. </span> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",PullRequestReviewComment) />Pull Request review comment <span class="help-block normal">Pull Request diff commented on. </span> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",PullRequest) />Pull request <span class="help-block normal">Pull request opened, closed<!-- , assigned, labeled -->, or synchronized. </span> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",PullRequestReviewComment) />Pull request review comment <span class="help-block normal">Pull request diff commented on. </span> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",Push) />Push <span class="help-block normal">Git push to a repository. </span> </label>
|
||||
<div class="text-right">
|
||||
@if(!create){
|
||||
|
||||
@@ -39,41 +39,6 @@
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Features</div>
|
||||
<div class="panel-body">
|
||||
<fieldset class="form-group">
|
||||
<label class="checkbox" for="enableIssues">
|
||||
<input type="checkbox" id="enableIssues" name="enableIssues"@if(repository.repository.options.enableIssues){ checked}/>
|
||||
Issues<br>
|
||||
<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.
|
||||
</div>
|
||||
</label>
|
||||
<label for="externalIssuesUrl" class="strong">External URL:
|
||||
<span class="normal muted">(Put if you have the external issue tracking system for this project)</span>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="externalIssuesUrl" name="externalIssuesUrl" value="@repository.repository.options.externalIssuesUrl"/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label class="checkbox" for="enableWiki">
|
||||
<input type="checkbox" id="enableWiki" name="enableWiki"@if(repository.repository.options.enableWiki){ checked}/>
|
||||
Wiki<br>
|
||||
<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.
|
||||
</div>
|
||||
</label>
|
||||
<label class="checkbox" for="allowWikiEditing">
|
||||
<input type="checkbox" id="allowWikiEditing" name="allowWikiEditing"@if(repository.repository.options.allowWikiEditing){ checked}/>
|
||||
Allow read-only users to edit Wiki pages<br>
|
||||
</label>
|
||||
<label for="externalWikiUrl" class="strong">External URL:
|
||||
<span class="normal muted">(Put if you have the external Wiki for this project)</span>
|
||||
</label>
|
||||
<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}/>
|
||||
@@ -85,6 +50,68 @@
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Issues</div>
|
||||
<div class="panel-body">
|
||||
<fieldset class="form-group">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="issuesOption" value="DISABLE" @if(repository.repository.options.issuesOption == "DISABLE"){ checked}> Disables issues tracking system
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="issuesOption" value="PRIVATE" @if(repository.repository.options.issuesOption == "PRIVATE"){ checked}> Developers can view, create and comment on issues
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="issuesOption" value="PUBLIC" @if(repository.repository.options.issuesOption == "PUBLIC"){ checked}> Developers and guests can view, create and comment on isues
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio for-public-repo">
|
||||
<label>
|
||||
<input type="radio" name="issuesOption" value="ALL" @if(repository.repository.options.issuesOption == "ALL"){ checked}> All users can view, create and comment on isues
|
||||
</label>
|
||||
</div>
|
||||
<label for="externalIssuesUrl" class="strong">External URL:
|
||||
<span class="normal muted">(Put if you have the external issue tracking system for this project)</span>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="externalIssuesUrl" name="externalIssuesUrl" value="@repository.repository.options.externalIssuesUrl"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Wiki</div>
|
||||
<div class="panel-body">
|
||||
<fieldset class="form-group">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="wikiOption" value="DISABLE" @if(repository.repository.options.wikiOption == "DISABLE"){ checked}> Disables wiki
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="wikiOption" value="PRIVATE" @if(repository.repository.options.wikiOption == "PRIVATE"){ checked}> Developers can view, create and edit wiki pages
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="wikiOption" value="PUBLIC" @if(repository.repository.options.wikiOption == "PUBLIC"){ checked}> Developers and guests can view, create and edit wiki pages
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio for-public-repo">
|
||||
<label>
|
||||
<input type="radio" name="wikiOption" value="ALL" @if(repository.repository.options.issuesOption == "ALL"){ checked}> All users can view, create and comment on isues
|
||||
</label>
|
||||
</div>
|
||||
<label for="externalWikiUrl" class="strong">External URL:
|
||||
<span class="normal muted">(Put if you have the external Wiki for this project)</span>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="externalWikiUrl" name="externalWikiUrl" value="@repository.repository.options.externalWikiUrl"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div class="align-right" style="margin-top: 20px;">
|
||||
<input type="submit" class="btn btn-success" value="Apply changes"/>
|
||||
</div>
|
||||
@@ -95,15 +122,25 @@
|
||||
<script>
|
||||
$(function(){
|
||||
updateFeatures();
|
||||
|
||||
$('#enableIssues, #enableWiki').click(function(){
|
||||
$('input[name=isPrivate], input[name=issuesOption], input[name=wikiOption]').click(function(){
|
||||
updateFeatures();
|
||||
});
|
||||
});
|
||||
|
||||
function updateFeatures() {
|
||||
$('#externalIssuesUrl').prop('disabled', $('#enableIssues').prop('checked'));
|
||||
$('#allowWikiEditing').prop('disabled', !$('#enableWiki').prop('checked'));
|
||||
$('#externalWikiUrl').prop('disabled', $('#enableWiki').prop('checked'));
|
||||
if($('input[name=isPrivate]:checked').val() == 'false'){
|
||||
$('.for-public-repo').show();
|
||||
} else {
|
||||
if($('input[name=issuesOption]:checked').val() == 'ALL'){
|
||||
$('input[name=issuesOption][value=PUBLIC]').prop('checked', true);
|
||||
}
|
||||
if($('input[name=wikiOption]:checked').val() == 'ALL'){
|
||||
$('input[name=wikiOption][value=PUBLIC]').prop('checked', true);
|
||||
}
|
||||
$('.for-public-repo').hide();
|
||||
}
|
||||
|
||||
$('#externalIssuesUrl').prop('disabled', $('input[name=issuesOption]:checked').val() != 'DISABLE');
|
||||
$('#externalWikiUrl').prop('disabled', $('input[name=wikiOption]:checked').val() != 'DISABLE');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
to: String,
|
||||
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean,
|
||||
isEditable: Boolean,
|
||||
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="pull-left">
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, None, None, false, None, false, false)
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
@if(isEditable){
|
||||
<div>
|
||||
@if(pageName.isDefined){
|
||||
<a href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_revert/@from...@to" class="btn">Revert Changes</a>
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
<div class="pull-right">
|
||||
@if(page.isDefined){
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a>
|
||||
<a class="btn btn-small btn-danger" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_delete" id="delete">Delete Page</a>
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a>
|
||||
<a class="btn btn-danger" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_delete" id="delete">Delete Page</a>
|
||||
}
|
||||
<a class="btn btn-small btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
</div>
|
||||
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||
<form action="@helpers.url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
|
||||
@@ -47,6 +46,19 @@
|
||||
<script>
|
||||
$(function(){
|
||||
try {
|
||||
$('#content1').dropzone({
|
||||
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
|
||||
maxFilesize: 10,
|
||||
clickable: false,
|
||||
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
|
||||
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
|
||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||
success: function(file, id) {
|
||||
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
|
||||
$('#content1').val($('#content1').val() + attachFile);
|
||||
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
|
||||
}
|
||||
});
|
||||
$('.clickable').dropzone({
|
||||
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
|
||||
maxFilesize: 10,
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
@(pageName: Option[String],
|
||||
commits: List[gitbucket.core.util.JGitUtil.CommitInfo],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
isEditable: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"History - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
<div class="pull-right">
|
||||
@if(pageName.isEmpty){
|
||||
@if(context.loginAccount.isDefined){
|
||||
<a class="btn btn-small" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
} else {
|
||||
@if(context.loginAccount.isDefined){
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_edit">Edit Page</a>
|
||||
<a class="btn btn-small btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
@if(isEditable) {
|
||||
<div class="pull-right">
|
||||
@if(pageName.isEmpty) {
|
||||
<a class="btn" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
} else {
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_edit">Edit Page</a>
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<h1 class="wiki-title">
|
||||
@if(pageName.isEmpty){
|
||||
<span class="muted">History</span>
|
||||
@@ -29,7 +28,7 @@
|
||||
<th colspan="3">
|
||||
<div class="pull-left" style="padding-top: 4px;">Revisions</div>
|
||||
<div class="pull-right">
|
||||
<input type="button" id="compare" value="Compare Revisions" class="btn btn-dm btn-default"/>
|
||||
<input type="button" id="compare" value="Compare Revisions" class="btn btn-sm btn-default"/>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
page: gitbucket.core.service.WikiService.WikiPageInfo,
|
||||
pages: List[String],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean,
|
||||
isEditable: Boolean,
|
||||
sidebar: Option[gitbucket.core.service.WikiService.WikiPageInfo],
|
||||
footer: Option[gitbucket.core.service.WikiService.WikiPageInfo])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@@ -10,13 +10,20 @@
|
||||
@gitbucket.core.html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
<div>
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a>
|
||||
@if(hasWritePermission){
|
||||
<form method="GET" action="@helpers.url(repository)/search" id="search-filter-form" class="form-inline pull-right">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="q" placeholder="Search..."/>
|
||||
<input type="hidden" name="type" value="wiki"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" id="search-btn" class="btn btn-default"><i class="fa fa-search"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a>
|
||||
@if(isEditable){
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_edit">Edit Page</a>
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
<h1 class="body-title">@pageName</h1>
|
||||
<div>
|
||||
<span class="muted"><strong>@page.committer</strong> edited this page @gitbucket.core.helper.html.datetimeago(page.time)</span>
|
||||
@@ -49,13 +56,13 @@
|
||||
}
|
||||
@sidebar.map { sidebarPage =>
|
||||
<div class="wiki-sidebar">
|
||||
@if(hasWritePermission){
|
||||
@if(isEditable){
|
||||
<a href="@helpers.url(repository)/wiki/_Sidebar/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||
}
|
||||
@helpers.markdown(sidebarPage.content, repository, true, false, false, false, pages)
|
||||
</div>
|
||||
}.getOrElse{
|
||||
@if(hasWritePermission){
|
||||
@if(isEditable){
|
||||
<a class="button-link" href="@helpers.url(repository)/wiki/_Sidebar/_edit" style="text-decoration: none;">
|
||||
<div class="wiki-sidebar-dotted text-center"><i class="octicon octicon-plus"></i> Add a custom sidebar</div>
|
||||
</a>
|
||||
@@ -64,7 +71,7 @@
|
||||
<div>
|
||||
<strong>Clone this wiki locally</strong>
|
||||
</div>
|
||||
@gitbucket.core.helper.html.copy("repository-url-copy", WikiService.wikiHttpUrl(repository)){
|
||||
@gitbucket.core.helper.html.copy("repository-url", "repository-url-copy", WikiService.wikiHttpUrl(repository)){
|
||||
<input type="text" value="@WikiService.wikiHttpUrl(repository)" id="repository-url" class="form-control input-sm" readonly>
|
||||
}
|
||||
@if(WikiService.wikiSshUrl(repository).isDefined) {
|
||||
@@ -88,13 +95,13 @@
|
||||
</div>
|
||||
@footer.map { footerPage =>
|
||||
<div class="wiki-sidebar wiki-footer">
|
||||
@if(hasWritePermission){
|
||||
@if(isEditable){
|
||||
<a href="@helpers.url(repository)/wiki/_Footer/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||
}
|
||||
@helpers.markdown(footerPage.content, repository, true, false, false, false, pages)
|
||||
</div>
|
||||
}.getOrElse{
|
||||
@if(hasWritePermission){
|
||||
@if(isEditable){
|
||||
<a class="button-link" href="@helpers.url(repository)/wiki/_Footer/_edit" style="text-decoration: none;">
|
||||
<div class="wiki-sidebar-dotted text-center"><i class="octicon octicon-plus"></i> Add a custom footer</div>
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@(pages: List[String],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
isEditable: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("wiki", repository){
|
||||
@@ -9,11 +9,9 @@
|
||||
<h1 class="wiki-title"><span class="muted">Pages</span></h1>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<div class="btn-group">
|
||||
@if(hasWritePermission){
|
||||
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</div>
|
||||
@if(isEditable){
|
||||
<a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a>
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="pull-left">
|
||||
|
||||
@@ -174,12 +174,6 @@ span.error {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.shorten-text {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a.commit-message, a.commit-id, a.username, a.issue-comment-count {
|
||||
color: #333333;
|
||||
}
|
||||
@@ -299,6 +293,20 @@ th.box-header .octicon {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
td.ellipsis-cell {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
td.ellipsis-cell > * {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-right: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
img.avatar {
|
||||
border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
@@ -326,6 +334,12 @@ div.account-image {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dropdown-filter-input {
|
||||
border: solid 1px #ccc;
|
||||
margin: 4px;
|
||||
width: 96%;
|
||||
}
|
||||
|
||||
ul.dropdown-menu {
|
||||
padding: 2px 0;
|
||||
}
|
||||
@@ -338,7 +352,7 @@ ul.dropdown-menu li a {
|
||||
padding: 2px 10px;
|
||||
}
|
||||
|
||||
ul.dropdown-menu :last-child {
|
||||
ul.dropdown-menu li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@@ -632,12 +646,6 @@ span.simplified-path {
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
#branch-control-input {
|
||||
border: solid 1px #ccc;
|
||||
margin: 4px;
|
||||
width: 96%;
|
||||
}
|
||||
|
||||
.new-branch-name {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
@@ -779,10 +787,6 @@ h4#issueTitle {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
div.issue-avatar-image {
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.issue-participants {
|
||||
margin-bottom: 15px;
|
||||
margin-left: 50px;
|
||||
@@ -790,7 +794,6 @@ div.issue-participants {
|
||||
|
||||
div.issue-comment-box, div.commit-comment-box {
|
||||
margin-bottom: 15px;
|
||||
margin-left: 70px;
|
||||
}
|
||||
|
||||
div.issue-comment-box > div.panel-body,
|
||||
@@ -879,7 +882,7 @@ li.task-list-item input.task-list-item-checkbox {
|
||||
|
||||
.discussion-item {
|
||||
position: relative;
|
||||
margin: 15px 0 15px 79px;
|
||||
margin: 15px 0 15px 20px;
|
||||
padding-left: 25px;
|
||||
}
|
||||
.discussion-item-header {
|
||||
@@ -946,7 +949,7 @@ pre.reset.discussion-item-content-text{
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Pull Request */
|
||||
/* Pull request */
|
||||
/****************************************************************************/
|
||||
div.pullreq-info {
|
||||
border: 1px solid #ddd;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);
|
||||
/*!
|
||||
* AdminLTE v2.3.6
|
||||
* AdminLTE v2.3.8
|
||||
* Author: Almsaeed Studio
|
||||
* Website: Almsaeed Studio <http://almsaeedstudio.com>
|
||||
* License: Open source - MIT
|
||||
@@ -543,7 +543,14 @@ a:focus {
|
||||
padding: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.sidebar-menu li.active > a > .fa-angle-left > a > .pull-right-container > .fa-angle-left {
|
||||
.sidebar-menu li > a > .fa-angle-left {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.sidebar-menu li.active > a > .fa-angle-left,
|
||||
.sidebar-menu li.active > a > .pull-right-container > .fa-angle-left {
|
||||
-webkit-transform: rotate(-90deg);
|
||||
-ms-transform: rotate(-90deg);
|
||||
-o-transform: rotate(-90deg);
|
||||
@@ -1269,7 +1276,8 @@ a:focus {
|
||||
.form-group.has-success label {
|
||||
color: #00a65a;
|
||||
}
|
||||
.form-group.has-success .form-control {
|
||||
.form-group.has-success .form-control,
|
||||
.form-group.has-success .input-group-addon {
|
||||
border-color: #00a65a;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -1279,7 +1287,8 @@ a:focus {
|
||||
.form-group.has-warning label {
|
||||
color: #f39c12;
|
||||
}
|
||||
.form-group.has-warning .form-control {
|
||||
.form-group.has-warning .form-control,
|
||||
.form-group.has-warning .input-group-addon {
|
||||
border-color: #f39c12;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -1289,7 +1298,8 @@ a:focus {
|
||||
.form-group.has-error label {
|
||||
color: #dd4b39;
|
||||
}
|
||||
.form-group.has-error .form-control {
|
||||
.form-group.has-error .form-control,
|
||||
.form-group.has-error .input-group-addon {
|
||||
border-color: #dd4b39;
|
||||
box-shadow: none;
|
||||
}
|
||||
7
src/main/webapp/assets/vendors/AdminLTE-2.3.8/css/AdminLTE.min.css
vendored
Normal file
7
src/main/webapp/assets/vendors/AdminLTE-2.3.8/css/AdminLTE.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user