Compare commits

...

59 Commits
4.7 ... 4.8

Author SHA1 Message Date
Naoki Takezoe
2904bcf4a7 GitBucket 4.8 release 2016-12-23 18:07:15 +09:00
Naoki Takezoe
6630fa2f37 Fix file attachement bug 2016-12-22 15:49:15 +09:00
Naoki Takezoe
351e63e7b6 (refs #1386)Bump jQuery to 1.12.2 2016-12-22 14:24:16 +09:00
Naoki Takezoe
ea0f35a0a1 (refs #1375)Remove debug log 2016-12-19 19:39:20 +09:00
Naoki Takezoe
623c53e169 (refs #1375)Cache commit count 2016-12-19 19:38:07 +09:00
Naoki Takezoe
3e6fd2caf8 Merged branch master into master 2016-12-19 11:51:17 +09:00
Naoki Takezoe
39f1aa4487 (refs #1378)Don't apply text decorator plugins to formatted text 2016-12-19 11:51:02 +09:00
Naoki Takezoe
8ffd905a9f Merge pull request #1385 from kw-udon/single-issue-api
Add API to get a single issue
2016-12-19 10:58:28 +09:00
Keiichi Watanabe
668f9ef919 Fix content-type of get-contents API 2016-12-19 01:56:42 +09:00
Keiichi Watanabe
ffb9bb10f5 Add API to get a single issue
cf. https://developer.github.com/v3/issues/#get-a-single-issue
2016-12-19 01:42:13 +09:00
Naoki Takezoe
2618f54442 Add search form on issues and wiki 2016-12-19 00:09:16 +09:00
Naoki Takezoe
6b3218dd43 (refs #1370)Update search interface 2016-12-18 10:22:08 +09:00
Naoki Takezoe
56a9b7b0f1 (refs #1370)Search repository by name 2016-12-18 00:41:18 +09:00
Naoki Takezoe
4f4afc5686 Show the number of commits of selected branch 2016-12-17 20:22:30 +09:00
Naoki Takezoe
87fb136b85 Merge pull request #1376 from gitbucket/cut-down-too-long-text
(refs #1282) Fix text-ellipsis which does't work
2016-12-14 09:46:57 +09:00
Naoki Takezoe
7af271e14a Format code 2016-12-12 22:54:43 +09:00
Naoki Takezoe
f44d44cb4a Merge pull request #1373 from gitbucket/keep_pull_request_comment
(refs #1348)Keep pull request comment if new commits are pushed
2016-12-12 22:36:42 +09:00
Shunsuke Tadokoro
e7fc5f1753 (refs #1282) Fix text-ellipsis which does't work 2016-12-12 21:25:45 +09:00
Naoki Takezoe
f0e2775861 (refs #1348)Show commentted filename, commit id and pull request id on the header 2016-12-12 17:50:59 +09:00
Naoki Takezoe
2488ab9bd4 Merge pull request #1374 from gitbucket/improve-search-peformance
Don't search for undisplayed tabs
2016-12-12 14:18:52 +09:00
Naoki Takezoe
f0872d410c Replace the search tabs with the select box 2016-12-12 13:59:01 +09:00
Naoki Takezoe
9d69cc9d45 Don't search for undisplayed tabs 2016-12-12 12:15:41 +09:00
Naoki Takezoe
1c66052372 (refs #1348)Keep comments on the old line 2016-12-12 01:30:10 +09:00
Naoki Takezoe
158f799ca1 (refs #1348)Improve efficiency of updating comment positions 2016-12-11 21:57:37 +09:00
Naoki Takezoe
907532fd13 (refs #1348)Clean up 2016-12-11 17:17:41 +09:00
Naoki Takezoe
0f6a433623 (refs #1348)Fix testcase 2016-12-11 16:49:47 +09:00
Naoki Takezoe
00eab5d584 (refs #1348)Keep pull request comment if new commits are pushed 2016-12-11 14:17:11 +09:00
Naoki Takezoe
5d928b1a62 Fix code format 2016-12-10 23:40:40 +09:00
Naoki Takezoe
50d6f0c96f Merge pull request #1371 from gitbucket/api_pull_request_model
(refs #1271)Add some properties to ApiPullRequest model
2016-12-10 12:24:40 +09:00
Naoki Takezoe
a60b43b862 (refs #1271)Fix testcase 2016-12-10 12:14:24 +09:00
Naoki Takezoe
4b1235b484 (refs #1271)Fixup 2016-12-10 11:25:16 +09:00
Naoki Takezoe
f354b9cfd7 (refs #1271)Add merged_by property 2016-12-10 02:25:39 +09:00
Naoki Takezoe
0c2283ce28 (refs #1271)Add some properties to ApiPullRequest model 2016-12-09 20:19:55 +09:00
Naoki Takezoe
840479a022 Merge pull request #1368 from tomoki1207/master
Fixed username suggestion on issue/PR.
2016-12-09 02:23:30 +09:00
Naoki Takezoe
1bceaaab1d (refs #1337)Fixup 2016-12-08 21:43:10 +09:00
Naoki Takezoe
65ece3292a (refs #1337)Use the specified port number as the SSL port number as well 2016-12-08 21:34:12 +09:00
Naoki Takezoe
e410623cac (refs #1367)Allow fast-forward push even if branch is protected 2016-12-08 21:06:18 +09:00
tomoki1207
09f7f036aa Fixed user name suggestion on issue/PR. 2016-12-06 18:58:02 +09:00
Naoki Takezoe
5249224dec Merge pull request #1365 from uli-heller/issue-1364-inconsistent-labels
Use lowercase for the first character of most 2nd words
2016-12-04 21:42:41 +09:00
Naoki Takezoe
00def3a46d (refs #1357)Fix error page 2016-12-04 13:28:26 +09:00
Uli Heller
134c0010b5 Use lowercase for the first character of most 2nd words
Unchanged: Mail Address, Full Name
2016-12-03 10:39:38 +01:00
Naoki Takezoe
fe3b40557a Tweak arguments of copy.scala.html 2016-12-01 14:58:30 +09:00
Naoki Takezoe
d3cdc5d5fc (refs #1359)Fix AdminLTE JavaScript importing 2016-12-01 09:27:20 +09:00
Naoki Takezoe
7ebb28be74 Remove ZeroClipboard 2016-12-01 02:07:24 +09:00
Naoki Takezoe
707cd3c5c3 Merge pull request #1359 from UprootStaging/adminlte-update
Updated admin lte to 2.3.8
2016-12-01 00:33:48 +09:00
Naoki Takezoe
4c5017d108 Merge pull request #1358 from tksugimoto/enable-copy-button-without-flash
Enable copy button without flash if JavaScript copy command is enable
2016-12-01 00:32:40 +09:00
hrj
fdd91b1e0e Updated admin lte to 2.3.8 2016-11-29 10:33:08 +05:30
Naoki Takezoe
9124777ce7 Merge pull request #1354 from mark-velez/fix-apostrophe-in-issue-text
Prevent LinkConverter matching escaped apostrophe (fixes #1148)
2016-11-28 18:41:53 +09:00
Naoki Takezoe
7c575fdc52 Update README.md 2016-11-28 00:17:12 +09:00
Naoki Takezoe
f9d6f1334f Update README.md 2016-11-28 00:16:06 +09:00
Naoki Takezoe
65d4900325 Update version to 4.7.1 2016-11-28 00:08:44 +09:00
Takashi Sugimoto
64248d1fce Enable copy button without flash 2016-11-27 23:18:35 +09:00
Naoki Takezoe
bec75120bc Update version to 4.7.1 2016-11-27 19:09:45 +09:00
Naoki Takezoe
3b0eed48d9 (refs #1355)Fix getUserRepositories() and getAllRepositories() too 2016-11-27 19:03:58 +09:00
Naoki Takezoe
25c4b1e6a7 (refs #1355)Fix RepositoryService#getVisibleRepositories() condition 2016-11-27 16:42:28 +09:00
Naoki Takezoe
cccff46715 (refs #1355)Fix RepositoryService#getVisibleRepositories() to contain group repositories 2016-11-27 04:03:23 +09:00
Matthieu Brouillard
fc0ffd1b4f Merge pull request #1356 from uli-heller/typo_ans_guests
Fixed typo in 4.7: '... ans guests...' -> '... and guests...'
2016-11-26 12:25:06 +01:00
Uli Heller
b70a2a2327 Fixed typo 2016-11-26 11:24:28 +01:00
Mark Velez
b5ca7ca0e1 Prevent LinkConverter matching escaped apostrophe (fixes #1148) 2016-11-26 02:17:51 -05:00
130 changed files with 849 additions and 11264 deletions

View File

@@ -65,6 +65,18 @@ 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

View File

@@ -1,6 +1,6 @@
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.7.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",

View File

@@ -22,5 +22,7 @@ object GitBucketCoreModule extends Module("gitbucket-core",
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")
)

View File

@@ -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

View File

@@ -22,6 +22,7 @@ class ApiController extends ApiControllerBase
with IssuesService
with LabelsService
with PullRequestService
with CommitsService
with CommitStatusService
with RepositoryCreationService
with HandleCommentService
@@ -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)))
}
@@ -254,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){
@@ -276,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()
@@ -363,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()
}
@@ -407,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)
)
})
})
@@ -421,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()
})
@@ -450,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)
}
}
@@ -469,14 +494,14 @@ trait ApiControllerBase extends ControllerBase {
*/
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()
@@ -514,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)))

View File

@@ -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))
}

View File

@@ -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)
}
}
@@ -142,15 +130,10 @@ trait IndexControllerBase extends ControllerBase {
} 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 {
@@ -159,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)
}
}
}

View File

@@ -14,12 +14,33 @@ 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 WritableUsersAuthenticator 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 WritableUsersAuthenticator 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])

View File

@@ -11,10 +11,7 @@ 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
@@ -498,26 +495,6 @@ 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)

View File

@@ -238,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

View File

@@ -328,7 +328,7 @@ 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),
getCommitComments(repository.owner, repository.name, id, true),
repository, diffs, oldCommitId, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
}
}
@@ -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, hasDeveloperRole(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, hasDeveloperRole(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()
}

View File

@@ -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; });"""
}

View File

@@ -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

View File

@@ -13,6 +13,7 @@ import scala.slick.jdbc.{StaticQuery => Q}
import Q.interpolation
trait IssuesService {
self: AccountService with RepositoryService =>
import IssuesService._
@@ -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 =>

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -223,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
@@ -232,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
@@ -242,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){
@@ -278,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)
@@ -412,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)
@@ -445,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"

View File

@@ -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)
}
}
}

View File

@@ -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}")

View File

@@ -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
}
@@ -877,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
@@ -959,6 +996,7 @@ object JGitUtil {
/**
* Returns sha1
*
* @param owner repository owner
* @param name repository name
* @param revstr A git object references expression

View File

@@ -109,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))

View File

@@ -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) =>

View File

@@ -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 = {

View File

@@ -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;">

View File

@@ -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>
}
@@ -136,4 +136,4 @@ $(function(){
$('#members').val(members);
}
});
</script>
</script>

View File

@@ -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>
}

View File

@@ -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 =>

View File

@@ -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 -->

View File

@@ -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>

View File

@@ -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">
@@ -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>
@@ -127,4 +127,4 @@ $(function(){
$('#members').val(members);
}
});
</script>
</script>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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 =>

View File

@@ -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>
}

View File

@@ -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>",

View File

@@ -13,15 +13,14 @@
@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">

View File

@@ -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>

View File

@@ -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);

View File

@@ -21,15 +21,22 @@
<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(isEditable){
@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), isManageable)
@if(isManageable){

View File

@@ -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,7 +36,7 @@
@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 @if(context.sidebarCollapse){sidebar-collapse}">
<div class="wrapper">
@@ -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>

View File

@@ -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.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 {

View File

@@ -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">

View File

@@ -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>

View File

@@ -14,7 +14,7 @@
@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>
@@ -99,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

View File

@@ -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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View 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>
}
}
}

View File

@@ -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>

View File

@@ -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){

View File

@@ -97,7 +97,7 @@
</div>
<div class="radio">
<label>
<input type="radio" name="wikiOption" value="PUBLIC" @if(repository.repository.options.wikiOption == "PUBLIC"){ checked}> Developers ans guests can view, create and edit wiki pages
<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">

View File

@@ -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">
@@ -44,9 +43,22 @@
</form>
}
}
<script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script>
<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,

View File

@@ -8,12 +8,12 @@
@if(isEditable) {
<div class="pull-right">
@if(pageName.isEmpty) {
<a class="btn btn-small" href="@helpers.url(repository)/wiki/_new">New Page</a>
<a class="btn" href="@helpers.url(repository)/wiki/_new">New Page</a>
} else {
<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>
<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){
@@ -28,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>

View File

@@ -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>
<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>
@@ -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) {

View File

@@ -9,11 +9,9 @@
<h1 class="wiki-title"><span class="muted">Pages</span></h1>
</li>
<li class="pull-right">
<div class="btn-group">
@if(isEditable){
<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">

View File

@@ -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;
@@ -941,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

View File

@@ -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;
}

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Some files were not shown because too many files have changed in this diff Show More