mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 03:37:35 +02:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
165cf88219 | ||
|
|
80b7e15d94 | ||
|
|
6eadebede2 | ||
|
|
beb0401500 | ||
|
|
eface25cf8 | ||
|
|
9eff4cb485 | ||
|
|
c65c3e2c49 | ||
|
|
d064ca85fb | ||
|
|
df9c34bcec | ||
|
|
172701105a | ||
|
|
e2da18a763 | ||
|
|
77383c4e8f | ||
|
|
aba9db3857 | ||
|
|
d97677aaaa | ||
|
|
d02a4baf47 | ||
|
|
4ce07ee3dd | ||
|
|
a354522406 | ||
|
|
c4bdf86253 | ||
|
|
87fa283b65 | ||
|
|
71828e5d08 | ||
|
|
89bf8db087 | ||
|
|
2b2669978f | ||
|
|
e7493eff3b | ||
|
|
1adb0b7bcf | ||
|
|
587970a477 | ||
|
|
b45e6428c7 | ||
|
|
7c758cbdee | ||
|
|
ffc0b59a58 | ||
|
|
382250c243 | ||
|
|
489ba2cd17 | ||
|
|
2a489870a1 | ||
|
|
99f1eaf3d8 | ||
|
|
e1c7cd0965 | ||
|
|
fbe60a59d7 | ||
|
|
efdf27df6b | ||
|
|
9ffda21bfd | ||
|
|
8ee7270986 | ||
|
|
d95a6b8134 | ||
|
|
31d546fd5a | ||
|
|
9812f66b0d | ||
|
|
5ac8b87a76 | ||
|
|
0f52dc4d8c | ||
|
|
3c956ac03e | ||
|
|
d45cba30c0 | ||
|
|
0840081dc8 | ||
|
|
0c0da0cbf7 | ||
|
|
e3641d0bf7 | ||
|
|
1c118b8cd7 | ||
|
|
abf516682b | ||
|
|
72d07422a4 | ||
|
|
ecc50cd2ae | ||
|
|
acbcb60629 | ||
|
|
23a9bf46a2 | ||
|
|
342ad68212 | ||
|
|
6d3dec518f | ||
|
|
e350633b69 | ||
|
|
4e3be1deb5 | ||
|
|
dc290614ca | ||
|
|
1b22c2e29b | ||
|
|
eedbd9f45a | ||
|
|
fa29acef54 | ||
|
|
6355f8d0a4 | ||
|
|
173fc30211 | ||
|
|
4df9c36d82 | ||
|
|
33c0fb680e | ||
|
|
378b3986dc | ||
|
|
f321d0974e | ||
|
|
227e2786e1 | ||
|
|
1a90fd86ff | ||
|
|
49f095bb26 | ||
|
|
b9acfc62c6 | ||
|
|
864df6cdac | ||
|
|
f0f4b8faa6 | ||
|
|
6350354942 | ||
|
|
bde66b2896 | ||
|
|
173669f75e | ||
|
|
f53497da56 | ||
|
|
70dbee839a | ||
|
|
59859359ea |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,6 +1,17 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.25.0 - 29 May 2018
|
||||
- Security improvement
|
||||
- Show mail address at the profile page
|
||||
- Task list on commit comments
|
||||
- More detailed editing history of issues and pull requests
|
||||
- Expose user public keys
|
||||
- Download repository improvements
|
||||
|
||||
### 4.24.1 - 1 May 2018
|
||||
- Fix bug in Web API authentication
|
||||
|
||||
### 4.24.0 - 30 Apr 2018
|
||||
- Diff for each review comment on pull requests
|
||||
- Extra mail addresses support
|
||||
|
||||
18
README.md
18
README.md
@@ -1,4 +1,4 @@
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket) [](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.12)
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket) [](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.12) [](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
|
||||
=========
|
||||
|
||||
GitBucket is a Git web platform powered by Scala offering:
|
||||
@@ -68,14 +68,14 @@ Support
|
||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.24.x
|
||||
What's New in 4.25.x
|
||||
-------------
|
||||
### 4.24.0 - 30 Apr 2018
|
||||
- Diff for each review comment on pull requests
|
||||
- Extra mail addresses support
|
||||
- Show tags at the commit list
|
||||
- Keep wrap mode of the online editor
|
||||
- Renew layout of gitbucket-gist-plugin
|
||||
- Web API of gitbucket-ci-plugin
|
||||
### 4.25.0 - 29 May 2018
|
||||
- Security improvement
|
||||
- Show mail address at the profile page
|
||||
- Task list on commit comments
|
||||
- More detailed editing history of issues and pull requests
|
||||
- Expose user public keys
|
||||
- Download repository improvements
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
@@ -3,7 +3,7 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.24.0"
|
||||
val GitBucketVersion = "4.25.0"
|
||||
val ScalatraVersion = "2.6.1"
|
||||
val JettyVersion = "9.4.7.v20170914"
|
||||
|
||||
@@ -47,7 +47,7 @@ libraryDependencies ++= Seq(
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.196",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.2.3",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.2.4",
|
||||
"org.postgresql" % "postgresql" % "42.1.4",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"com.zaxxer" % "HikariCP" % "2.7.4",
|
||||
|
||||
@@ -6,22 +6,26 @@ The details are saved at `ISSUE_COMMENT` table.
|
||||
To determine if it was any operation, you see the `ACTION` column.
|
||||
And in the case of some actions, `CONTENT` column value contains additional information.
|
||||
|
||||
|ACTION |CONTENT |
|
||||
|----------------|----------------------|
|
||||
|comment |comment |
|
||||
|close_comment |comment |
|
||||
|reopen_comment |comment |
|
||||
|close |"Close" |
|
||||
|reopen |"Reopen" |
|
||||
|commit |comment commitId |
|
||||
|merge |comment |
|
||||
|delete_branch |branchName |
|
||||
|refer |issueId:title |
|
||||
|add_label |labelName |
|
||||
|delete_label |labelName |
|
||||
|change_priority |oldPriority:priority |
|
||||
|change_milestone|oldMilestone:milestone|
|
||||
|assign |oldAssigned:assigned |
|
||||
|ACTION |CONTENT |
|
||||
|----------------|--------------------------|
|
||||
|comment |comment |
|
||||
|close_comment |comment |
|
||||
|reopen_comment |comment |
|
||||
|close |"Close" |
|
||||
|reopen |"Reopen" |
|
||||
|commit |comment commitId |
|
||||
|merge |comment |
|
||||
|delete_branch |branchName |
|
||||
|refer |issueId:title |
|
||||
|add_label |labelName |
|
||||
|delete_label |labelName |
|
||||
|change_priority |oldPriority:priority |
|
||||
|change_milestone|oldMilestone:milestone |
|
||||
|assign |oldAssigned:assigned |
|
||||
|change_title |oldTitle(CRLF)title \[1\] |
|
||||
|
||||
\[1\]: (CRLF) is "\r\n"
|
||||
|
||||
|
||||
### comment
|
||||
|
||||
@@ -79,3 +83,7 @@ This value is saved when users have changed the milestone.
|
||||
### assign
|
||||
|
||||
This value is saved when users have assign issue/PR to user or remove the assign.
|
||||
|
||||
### change_title
|
||||
|
||||
This value is saved when users have changed the title.
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
"description": "Provides Gist feature on GitBucket.",
|
||||
"versions": [
|
||||
{
|
||||
"version": "4.14.0",
|
||||
"range": ">=4.23.0",
|
||||
"url": "https://github.com/gitbucket/gitbucket-gist-plugin/releases/download/4.14.0/gitbucket-gist-plugin-assembly-4.14.0.jar"
|
||||
"version": "4.15.0",
|
||||
"range": ">=4.25.0",
|
||||
"url": "https://github.com/gitbucket/gitbucket-gist-plugin/releases/download/4.15.0/gitbucket-gist-plugin-gitbucket_4.25.0-4.15.0.jar"
|
||||
}
|
||||
],
|
||||
"default": false
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.1.3
|
||||
sbt.version=1.1.5
|
||||
|
||||
8
src/main/resources/update/gitbucket-core_4.25.xml
Normal file
8
src/main/resources/update/gitbucket-core_4.25.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<modifyDataType columnName="PASSWORD" newDataType="varchar(200)" tableName="ACCOUNT"/>
|
||||
|
||||
<delete tableName="ACCOUNT_EXTRA_MAIL_ADDRESS">
|
||||
<where>EXTRA_MAIL_ADDRESS = ''</where>
|
||||
</delete>
|
||||
</changeSet>
|
||||
@@ -52,5 +52,7 @@ object GitBucketCoreModule
|
||||
new Version("4.22.0", new LiquibaseMigration("update/gitbucket-core_4.22.xml")),
|
||||
new Version("4.23.0", new LiquibaseMigration("update/gitbucket-core_4.23.xml")),
|
||||
new Version("4.23.1"),
|
||||
new Version("4.24.0", new LiquibaseMigration("update/gitbucket-core_4.24.xml"))
|
||||
new Version("4.24.0", new LiquibaseMigration("update/gitbucket-core_4.24.xml")),
|
||||
new Version("4.24.1"),
|
||||
new Version("4.25.0", new LiquibaseMigration("update/gitbucket-core_4.25.xml"))
|
||||
)
|
||||
|
||||
@@ -229,13 +229,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName") {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
val extraMailAddresses = getAccountExtraMailAddresses(userName)
|
||||
params.getOrElse("tab", "repositories") match {
|
||||
// Public Activity
|
||||
case "activity" =>
|
||||
gitbucket.core.account.html.activity(
|
||||
account,
|
||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getActivitiesByUser(userName, true)
|
||||
getActivitiesByUser(userName, true),
|
||||
extraMailAddresses
|
||||
)
|
||||
|
||||
// Members
|
||||
@@ -244,6 +246,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
gitbucket.core.account.html.members(
|
||||
account,
|
||||
members,
|
||||
extraMailAddresses,
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
@@ -260,6 +263,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
account,
|
||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getVisibleRepositories(context.loginAccount, Some(userName)),
|
||||
extraMailAddresses,
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
@@ -278,6 +282,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
helper.xml.feed(getActivitiesByUser(userName, true))
|
||||
}
|
||||
|
||||
get("/:userName.keys") {
|
||||
val keys = getPublicKeys(params("userName"))
|
||||
contentType = "text/plain; charset=utf-8"
|
||||
keys.map(_.publicKey).mkString("", "\n", "\n")
|
||||
}
|
||||
|
||||
get("/:userName/_avatar") {
|
||||
val userName = params("userName")
|
||||
contentType = "image/png"
|
||||
@@ -318,7 +328,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
account =>
|
||||
updateAccount(
|
||||
account.copy(
|
||||
password = form.password.map(sha1).getOrElse(account.password),
|
||||
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
|
||||
fullName = form.fullName,
|
||||
mailAddress = form.mailAddress,
|
||||
description = form.description,
|
||||
@@ -559,7 +569,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
if (context.settings.allowAccountRegistration) {
|
||||
createAccount(
|
||||
form.userName,
|
||||
sha1(form.password),
|
||||
pbkdf2_sha256(form.password),
|
||||
form.fullName,
|
||||
form.mailAddress,
|
||||
false,
|
||||
@@ -567,7 +577,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
form.url
|
||||
)
|
||||
updateImage(form.userName, form.fileId, false)
|
||||
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses)
|
||||
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != ""))
|
||||
redirect("/signin")
|
||||
} else NotFound()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.servlet.Database
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
@@ -122,7 +124,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/*
|
||||
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/branches")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
JsonFormat(
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
@@ -140,7 +142,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
||||
//import gitbucket.core.api._
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
@@ -161,14 +163,14 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/*
|
||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/contents")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
||||
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
||||
})
|
||||
|
||||
/*
|
||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/contents/*")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||
})
|
||||
|
||||
@@ -183,7 +185,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
||||
}
|
||||
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git =>
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val fileList = getFileList(git, refStr, path)
|
||||
if (fileList.isEmpty) { // file or NotFound
|
||||
getFileInfo(git, refStr, path)
|
||||
@@ -242,9 +244,9 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/*
|
||||
* https://developer.github.com/v3/git/refs/#get-a-reference
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/git/refs/*")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
|
||||
val revstr = multiParams("splat").head
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git =>
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val ref = git.getRepository().findRef(revstr)
|
||||
|
||||
if (ref != null) {
|
||||
@@ -269,9 +271,11 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/collaborators")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/collaborators")(referrersOnly { repository =>
|
||||
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
||||
JsonFormat(getCollaboratorUserNames(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
|
||||
JsonFormat(
|
||||
getCollaboratorUserNames(params("owner"), params("repository")).map(u => ApiUser(getAccountByUserName(u).get))
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -313,7 +317,10 @@ trait ApiControllerBase extends ControllerBase {
|
||||
data.auto_init
|
||||
)
|
||||
Await.result(f, Duration.Inf)
|
||||
val repository = getRepository(owner, data.name).get
|
||||
|
||||
val repository = Database() withTransaction { session =>
|
||||
getRepository(owner, data.name)(session).get
|
||||
}
|
||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
||||
} else {
|
||||
ApiError(
|
||||
@@ -360,7 +367,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repo/branches/*")(ownerOnly { repository =>
|
||||
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
@@ -718,7 +725,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository =>
|
||||
post("/api/v3/repos/:owner/:repository/statuses/:sha")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
ref <- params.get("sha")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
@@ -747,7 +754,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*
|
||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||
*/
|
||||
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
|
||||
val listStatusesRoute = get("/api/v3/repos/:owner/:repository/commits/:ref/statuses")(referrersOnly { repository =>
|
||||
(for {
|
||||
ref <- params.get("ref")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
@@ -764,7 +771,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*
|
||||
* legacy route
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/statuses/:ref") {
|
||||
get("/api/v3/repos/:owner/:repository/statuses/:ref") {
|
||||
listStatusesRoute.action()
|
||||
}
|
||||
|
||||
@@ -773,7 +780,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
*
|
||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/commits/:ref/status")(referrersOnly { repository =>
|
||||
(for {
|
||||
ref <- params.get("ref")
|
||||
owner <- getAccountByUserName(repository.owner)
|
||||
@@ -787,7 +794,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/commits/#get-a-single-commit
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/commits/:sha")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/commits/:sha")(referrersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val sha = params("sha")
|
||||
@@ -839,7 +846,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* non-GitHub compatible API for Jenkins-Plugin
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/raw/*")(referrersOnly { repository =>
|
||||
get("/api/v3/repos/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
|
||||
@@ -9,7 +9,7 @@ import gitbucket.core.model.Account
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.{Keys, LDAPUtil, ReferrerAuthenticator, UsersAuthenticator}
|
||||
import gitbucket.core.util._
|
||||
import org.scalatra.Ok
|
||||
import org.scalatra.forms._
|
||||
|
||||
@@ -208,7 +208,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"<b>@${t.userName}</b> ${t.fullName}",
|
||||
"label" -> s"<b>@${StringUtil.escapeHtml(t.userName)}</b> ${StringUtil.escapeHtml(t.fullName)}",
|
||||
"value" -> t.userName
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,15 +154,25 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
getIssue(owner, name, params("id")).map {
|
||||
issue =>
|
||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
||||
if (issue.title != title) {
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
||||
createComment(
|
||||
owner,
|
||||
name,
|
||||
context.loginAccount.get.userName,
|
||||
issue.issueId,
|
||||
issue.title + "\r\n" + title,
|
||||
"change_title"
|
||||
)
|
||||
}
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -113,7 +113,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
val (commits, _) =
|
||||
val (commits, diffs) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
|
||||
html.conversation(
|
||||
@@ -121,6 +121,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
pullreq,
|
||||
commits.flatten,
|
||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
||||
diffs.size,
|
||||
getIssueLabels(owner, name, issueId),
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
@@ -157,23 +158,25 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
|
||||
params("id").toIntOpt.flatMap { issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
val (commits, _) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
params("id").toIntOpt.flatMap {
|
||||
issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
val (commits, diffs) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
|
||||
html.commits(
|
||||
issue,
|
||||
pullreq,
|
||||
commits,
|
||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
||||
isManageable(repository),
|
||||
repository
|
||||
)
|
||||
}
|
||||
html.commits(
|
||||
issue,
|
||||
pullreq,
|
||||
commits,
|
||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
||||
diffs.size,
|
||||
isManageable(repository),
|
||||
repository
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.repo.html
|
||||
import gitbucket.core.helper
|
||||
@@ -17,6 +17,13 @@ import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook}
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
|
||||
import org.apache.commons.compress.archivers.tar.{TarArchiveEntry, TarArchiveOutputStream}
|
||||
import org.apache.commons.compress.archivers.zip.{ZipArchiveEntry, ZipArchiveOutputStream}
|
||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream
|
||||
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
|
||||
import org.apache.commons.compress.utils.IOUtils
|
||||
import org.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
@@ -25,6 +32,8 @@ import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.errors.MissingObjectException
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.i18n.Messages
|
||||
@@ -705,6 +714,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
)
|
||||
@@ -812,16 +822,31 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
/**
|
||||
* Download repository contents as an archive.
|
||||
* Download repository contents as an archive as compatible URL.
|
||||
*/
|
||||
get("/:owner/:repository/archive/*")(referrersOnly { repository =>
|
||||
multiParams("splat").head match {
|
||||
case name if name.endsWith(".zip") =>
|
||||
archiveRepository(name, ".zip", repository)
|
||||
case name if name.endsWith(".tar.gz") =>
|
||||
archiveRepository(name, ".tar.gz", repository)
|
||||
case _ => BadRequest()
|
||||
}
|
||||
get("/:owner/:repository/archive/:branch.:suffix")(referrersOnly { repository =>
|
||||
val branch = params("branch")
|
||||
val suffix = params("suffix")
|
||||
archiveRepository(branch, branch + "." + suffix, repository, "")
|
||||
})
|
||||
|
||||
/**
|
||||
* Download all repository contents as an archive.
|
||||
*/
|
||||
get("/:owner/:repository/archive/:branch/:name")(referrersOnly { repository =>
|
||||
val branch = params("branch")
|
||||
val name = params("name")
|
||||
archiveRepository(branch, name, repository, "")
|
||||
})
|
||||
|
||||
/**
|
||||
* Download repositories subtree contents as an archive.
|
||||
*/
|
||||
get("/:owner/:repository/archive/:branch/*/:name")(referrersOnly { repository =>
|
||||
val branch = params("branch")
|
||||
val name = params("name")
|
||||
val path = multiParams("splat").head
|
||||
archiveRepository(branch, name, repository, path)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/network/members")(referrersOnly { repository =>
|
||||
@@ -1109,26 +1134,96 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): Unit = {
|
||||
val revision = name.stripSuffix(suffix)
|
||||
def archiveRepository(
|
||||
revision: String,
|
||||
filename: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
path: String
|
||||
) = {
|
||||
def archive(archiveFormat: String, archive: ArchiveOutputStream)(
|
||||
entryCreator: (String, Long, Int) => ArchiveEntry
|
||||
): Unit = {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val oid = git.getRepository.resolve(revision)
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, oid)
|
||||
val sha1 = oid.getName()
|
||||
val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-')
|
||||
val pathSuffix = if (path.isEmpty) "" else '-' + path.replace('/', '-')
|
||||
val baseName = repository.name + "-" + repositorySuffix + pathSuffix
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val oid = git.getRepository.resolve(revision)
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, oid)
|
||||
val sha1 = oid.getName()
|
||||
val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-')
|
||||
val filename = repository.name + "-" + repositorySuffix + suffix
|
||||
using(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
treeWalk.setRecursive(true)
|
||||
if (!path.isEmpty) {
|
||||
treeWalk.setFilter(PathFilter.create(path))
|
||||
}
|
||||
if (treeWalk != null) {
|
||||
while (treeWalk.next()) {
|
||||
val entryPath =
|
||||
if (path.isEmpty) baseName + "/" + treeWalk.getPathString
|
||||
else path.split("/").last + treeWalk.getPathString.substring(path.length)
|
||||
val size = JGitUtil.getFileSize(git, repository, treeWalk)
|
||||
val mode = treeWalk.getFileMode.getBits
|
||||
val entry: ArchiveEntry = entryCreator(entryPath, size, mode)
|
||||
JGitUtil.openFile(git, repository, revCommit.getTree, treeWalk.getPathString) { in =>
|
||||
archive.putArchiveEntry(entry)
|
||||
IOUtils.copy(in, archive)
|
||||
archive.closeArchiveEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentType = "application/octet-stream"
|
||||
response.setHeader("Content-Disposition", s"attachment; filename=${filename}")
|
||||
response.setBufferSize(1024 * 1024);
|
||||
val suffix = path.split("/").lastOption.map("-" + _).getOrElse("")
|
||||
val zipRe = """(.+)\.zip$""".r
|
||||
val tarRe = """(.+)\.tar\.(gz|bz2|xz)$""".r
|
||||
|
||||
git.archive
|
||||
.setFormat(suffix.tail)
|
||||
.setPrefix(repository.name + "-" + repositorySuffix + "/")
|
||||
.setTree(revCommit)
|
||||
.setOutputStream(response.getOutputStream)
|
||||
.call()
|
||||
filename match {
|
||||
case zipRe(branch) =>
|
||||
response.setHeader(
|
||||
"Content-Disposition",
|
||||
s"attachment; filename=${repository.name}-${branch}${suffix}.zip"
|
||||
)
|
||||
contentType = "application/octet-stream"
|
||||
response.setBufferSize(1024 * 1024);
|
||||
using(new ZipArchiveOutputStream(response.getOutputStream)) { zip =>
|
||||
archive(".zip", zip) { (path, size, mode) =>
|
||||
val entry = new ZipArchiveEntry(path)
|
||||
entry.setSize(size)
|
||||
entry.setUnixMode(mode)
|
||||
entry
|
||||
}
|
||||
}
|
||||
()
|
||||
case tarRe(branch, compressor) =>
|
||||
response.setHeader(
|
||||
"Content-Disposition",
|
||||
s"attachment; filename=${repository.name}-${branch}${suffix}.tar.${compressor}"
|
||||
)
|
||||
contentType = "application/octet-stream"
|
||||
response.setBufferSize(1024 * 1024)
|
||||
using(compressor match {
|
||||
case "gz" => new GzipCompressorOutputStream(response.getOutputStream)
|
||||
case "bz2" => new BZip2CompressorOutputStream(response.getOutputStream)
|
||||
case "xz" => new XZCompressorOutputStream(response.getOutputStream)
|
||||
}) { compressorOutputStream =>
|
||||
using(new TarArchiveOutputStream(compressorOutputStream)) { tar =>
|
||||
tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
|
||||
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
|
||||
tar.setAddPaxHeadersForNonAsciiNames(true)
|
||||
archive(".tar.gz", tar) { (path, size, mode) =>
|
||||
val entry = new TarArchiveEntry(path)
|
||||
entry.setSize(size)
|
||||
entry.setMode(mode)
|
||||
entry
|
||||
}
|
||||
}
|
||||
}
|
||||
()
|
||||
case _ =>
|
||||
BadRequest()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.mail.EmailException
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.forms._
|
||||
@@ -89,7 +90,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"jwsAlgorithm" -> trim(label("Signature algorithm", optional(text())))
|
||||
)(OIDC.apply)
|
||||
),
|
||||
"skinName" -> trim(label("AdminLTE skin name", text(required)))
|
||||
"skinName" -> trim(label("AdminLTE skin name", text(required))),
|
||||
"showMailAddress" -> trim(label("Show mail address", boolean()))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if (settings.ssh && settings.baseUrl.isEmpty) {
|
||||
@@ -312,7 +314,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"Test mail has been sent to: " + form.testAddress
|
||||
|
||||
} catch {
|
||||
case e: Exception => "[Error] " + e.toString
|
||||
case e: EmailException => s"[Error] ${e.getCause}"
|
||||
case e: Exception => "[Error] " + e.toString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -414,7 +417,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
||||
createAccount(
|
||||
form.userName,
|
||||
sha1(form.password),
|
||||
pbkdf2_sha256(form.password),
|
||||
form.fullName,
|
||||
form.mailAddress,
|
||||
form.isAdmin,
|
||||
@@ -454,7 +457,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
updateAccount(
|
||||
account.copy(
|
||||
password = form.password.map(sha1).getOrElse(account.password),
|
||||
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
|
||||
fullName = form.fullName,
|
||||
mailAddress = form.mailAddress,
|
||||
isAdmin = form.isAdmin,
|
||||
|
||||
@@ -5,13 +5,13 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.{AccessToken, Account}
|
||||
import gitbucket.core.util.StringUtil
|
||||
|
||||
import scala.util.Random
|
||||
import java.security.SecureRandom
|
||||
|
||||
trait AccessTokenService {
|
||||
|
||||
def makeAccessTokenString: String = {
|
||||
val bytes = new Array[Byte](20)
|
||||
Random.nextBytes(bytes)
|
||||
AccessTokenService.secureRandom.nextBytes(bytes)
|
||||
bytes.map("%02x".format(_)).mkString
|
||||
}
|
||||
|
||||
@@ -55,4 +55,6 @@ trait AccessTokenService {
|
||||
|
||||
}
|
||||
|
||||
object AccessTokenService extends AccessTokenService
|
||||
object AccessTokenService extends AccessTokenService {
|
||||
private val secureRandom = new SecureRandom()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,16 @@ trait AccountService {
|
||||
* Authenticate by internal database.
|
||||
*/
|
||||
private def defaultAuthentication(userName: String, password: String)(implicit s: Session) = {
|
||||
val pbkdf2re = """^\$pbkdf2-sha256\$(\d+)\$([0-9a-zA-Z+/=]+)\$([0-9a-zA-Z+/=]+)$""".r
|
||||
getAccountByUserName(userName).collect {
|
||||
case account if !account.isGroupAccount =>
|
||||
account.password match {
|
||||
case pbkdf2re(iter, salt, hash) if (pbkdf2_sha256(iter.toInt, salt, password) == hash) => Some(account)
|
||||
case p if p == sha1(password) =>
|
||||
updateAccount(account.copy(password = pbkdf2_sha256(password)))
|
||||
Some(account)
|
||||
case _ => None
|
||||
}
|
||||
case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account)
|
||||
} getOrElse None
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ trait RepositoryService { self: AccountService =>
|
||||
repository.originUserName.getOrElse(repository.userName),
|
||||
repository.originRepositoryName.getOrElse(repository.repositoryName)
|
||||
),
|
||||
getRepositoryManagers(repository.userName)
|
||||
getRepositoryManagers(repository.userName, repository.repositoryName)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -407,7 +407,7 @@ trait RepositoryService { self: AccountService =>
|
||||
if (withoutPhysicalInfo) {
|
||||
Nil
|
||||
} else {
|
||||
getRepositoryManagers(repository.userName)
|
||||
getRepositoryManagers(repository.userName, repository.repositoryName)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -485,18 +485,21 @@ trait RepositoryService { self: AccountService =>
|
||||
if (withoutPhysicalInfo) {
|
||||
Nil
|
||||
} else {
|
||||
getRepositoryManagers(repository.userName)
|
||||
getRepositoryManagers(repository.userName, repository.repositoryName)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] =
|
||||
/**
|
||||
* TODO It seems to be able to improve performance. For example, RequestCache can be used for getAccountByUserName call.
|
||||
*/
|
||||
private def getRepositoryManagers(userName: String, repositoryName: String)(implicit s: Session): Seq[String] =
|
||||
if (getAccountByUserName(userName).exists(_.isGroupAccount)) {
|
||||
getGroupMembers(userName).collect { case x if (x.isManager) => x.userName }
|
||||
} else {
|
||||
Seq(userName)
|
||||
}
|
||||
} ++ getCollaboratorUserNames(userName, repositoryName, Seq(Role.ADMIN))
|
||||
|
||||
/**
|
||||
* Updates the last activity date of the repository.
|
||||
|
||||
@@ -68,6 +68,7 @@ trait SystemSettingsService {
|
||||
}
|
||||
}
|
||||
props.setProperty(SkinName, settings.skinName.toString)
|
||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||
using(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
}
|
||||
@@ -144,7 +145,8 @@ trait SystemSettingsService {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
getValue(props, SkinName, "skin-blue")
|
||||
getValue(props, SkinName, "skin-blue"),
|
||||
getValue(props, ShowMailAddress, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -174,7 +176,8 @@ object SystemSettingsService {
|
||||
ldap: Option[Ldap],
|
||||
oidcAuthentication: Boolean,
|
||||
oidc: Option[OIDC],
|
||||
skinName: String
|
||||
skinName: String,
|
||||
showMailAddress: Boolean
|
||||
) {
|
||||
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
@@ -283,6 +286,7 @@ object SystemSettingsService {
|
||||
private val OidcClientSecret = "oidc.client_secret"
|
||||
private val OidcJwsAlgorithm = "oidc.jws_algorithm"
|
||||
private val SkinName = "skinName"
|
||||
private val ShowMailAddress = "showMailAddress"
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.{ByteArrayOutputStream, File, FileInputStream, InputStream}
|
||||
|
||||
import gitbucket.core.service.RepositoryService
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -1220,4 +1220,61 @@ object JGitUtil {
|
||||
Option(git.getRepository.resolve(revstr)).map(ObjectId.toString(_))
|
||||
}
|
||||
}
|
||||
|
||||
def getFileSize(git: Git, repository: RepositoryService.RepositoryInfo, treeWalk: TreeWalk): Long = {
|
||||
val attrs = treeWalk.getAttributes
|
||||
val loader = git.getRepository.open(treeWalk.getObjectId(0))
|
||||
if (attrs.containsKey("filter") && attrs.get("filter").getValue == "lfs") {
|
||||
val lfsAttrs = getLfsAttributes(loader)
|
||||
lfsAttrs.get("size").map(_.toLong).get
|
||||
} else {
|
||||
loader.getSize
|
||||
}
|
||||
}
|
||||
|
||||
def getFileSize(git: Git, repository: RepositoryService.RepositoryInfo, tree: RevTree, path: String): Long = {
|
||||
using(TreeWalk.forPath(git.getRepository, path, tree)) { treeWalk =>
|
||||
getFileSize(git, repository, treeWalk)
|
||||
}
|
||||
}
|
||||
|
||||
def openFile[T](git: Git, repository: RepositoryService.RepositoryInfo, treeWalk: TreeWalk)(
|
||||
f: InputStream => T
|
||||
): T = {
|
||||
val attrs = treeWalk.getAttributes
|
||||
val loader = git.getRepository.open(treeWalk.getObjectId(0))
|
||||
if (attrs.containsKey("filter") && attrs.get("filter").getValue == "lfs") {
|
||||
val lfsAttrs = getLfsAttributes(loader)
|
||||
if (lfsAttrs.nonEmpty) {
|
||||
val oid = lfsAttrs("oid").split(":")(1)
|
||||
|
||||
val file = new File(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))
|
||||
using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))) { in =>
|
||||
f(in)
|
||||
}
|
||||
} else {
|
||||
throw new NoSuchElementException("LFS attribute is empty.")
|
||||
}
|
||||
} else {
|
||||
using(loader.openStream()) { in =>
|
||||
f(in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def openFile[T](git: Git, repository: RepositoryService.RepositoryInfo, tree: RevTree, path: String)(
|
||||
f: InputStream => T
|
||||
): T = {
|
||||
using(TreeWalk.forPath(git.getRepository, path, tree)) { treeWalk =>
|
||||
openFile(git, repository, treeWalk)(f)
|
||||
}
|
||||
}
|
||||
|
||||
private def getLfsAttributes(loader: ObjectLoader): Map[String, String] = {
|
||||
val bytes = loader.getCachedBytes
|
||||
val text = new String(bytes, "UTF-8")
|
||||
|
||||
JGitUtil.getLfsObjects(text)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.net.{URLDecoder, URLEncoder}
|
||||
import java.util.Base64
|
||||
import java.security.SecureRandom
|
||||
import java.util.{Base64, UUID}
|
||||
|
||||
import org.mozilla.universalchardet.UniversalDetector
|
||||
import SyntaxSugars._
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import org.apache.commons.io.input.BOMInputStream
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
@@ -13,9 +16,33 @@ import scala.util.control.Exception._
|
||||
object StringUtil {
|
||||
|
||||
private lazy val BlowfishKey = {
|
||||
// last 4 numbers in current timestamp
|
||||
val time = System.currentTimeMillis.toString
|
||||
time.substring(time.length - 4)
|
||||
UUID.randomUUID().toString.substring(0, 16)
|
||||
}
|
||||
|
||||
def base64Encode(value: Array[Byte]): String = {
|
||||
Base64.getEncoder.encodeToString(value)
|
||||
}
|
||||
|
||||
def base64Decode(value: String): Array[Byte] = {
|
||||
Base64.getDecoder.decode(value)
|
||||
}
|
||||
|
||||
def pbkdf2_sha256(iter: Int, salt: String, value: String): String = {
|
||||
val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
val ks = new PBEKeySpec(value.toCharArray, base64Decode(salt), iter, 256)
|
||||
val s = keyFactory.generateSecret(ks)
|
||||
base64Encode(s.getEncoded)
|
||||
}
|
||||
|
||||
def pbkdf2_sha256(value: String) = {
|
||||
val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
val secureRandom = new SecureRandom
|
||||
val salt: Array[Byte] = new Array(32)
|
||||
secureRandom.nextBytes(salt)
|
||||
val iter = 100000
|
||||
val ks = new PBEKeySpec(value.toCharArray, salt, iter, 256)
|
||||
val s = keyFactory.generateSecret(ks)
|
||||
s"""$$pbkdf2-sha256$$${iter}$$${base64Encode(salt)}$$${base64Encode(s.getEncoded)}"""
|
||||
}
|
||||
|
||||
def sha1(value: String): String =
|
||||
|
||||
@@ -250,12 +250,12 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* Generates the url to the repository.
|
||||
*/
|
||||
def url(repository: RepositoryService.RepositoryInfo)(implicit context: Context): String =
|
||||
s"${context.path}/${repository.owner}/${repository.name}"
|
||||
s"${context.path}/${encodeRefName(repository.owner)}/${encodeRefName(repository.name)}"
|
||||
|
||||
/**
|
||||
* Generates the url to the account page.
|
||||
*/
|
||||
def url(userName: String)(implicit context: Context): String = s"${context.path}/${userName}"
|
||||
def url(userName: String)(implicit context: Context): String = s"${context.path}/${encodeRefName(userName)}"
|
||||
|
||||
/**
|
||||
* Returns the url to the root of assets.
|
||||
@@ -273,7 +273,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* If user does not exist or disabled, this method returns user name as text without link.
|
||||
*/
|
||||
def user(userName: String, mailAddress: String = "", styleClass: String = "")(implicit context: Context): Html =
|
||||
userWithContent(userName, mailAddress, styleClass)(Html(userName))
|
||||
userWithContent(userName, mailAddress, styleClass)(Html(StringUtil.escapeHtml(userName)))
|
||||
|
||||
/**
|
||||
* Generates the avatar link to the account page.
|
||||
@@ -316,44 +316,6 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
*/
|
||||
def isPast(date: Date): Boolean = System.currentTimeMillis > date.getTime
|
||||
|
||||
/**
|
||||
* Returns file type for AceEditor.
|
||||
*/
|
||||
def editorType(fileName: String): String = {
|
||||
fileName.toLowerCase match {
|
||||
case x if (x.endsWith(".bat")) => "batchfile"
|
||||
case x if (x.endsWith(".java")) => "java"
|
||||
case x if (x.endsWith(".scala")) => "scala"
|
||||
case x if (x.endsWith(".js")) => "javascript"
|
||||
case x if (x.endsWith(".css")) => "css"
|
||||
case x if (x.endsWith(".md")) => "markdown"
|
||||
case x if (x.endsWith(".html")) => "html"
|
||||
case x if (x.endsWith(".xml")) => "xml"
|
||||
case x if (x.endsWith(".c")) => "c_cpp"
|
||||
case x if (x.endsWith(".cpp")) => "c_cpp"
|
||||
case x if (x.endsWith(".coffee")) => "coffee"
|
||||
case x if (x.endsWith(".ejs")) => "ejs"
|
||||
case x if (x.endsWith(".hs")) => "haskell"
|
||||
case x if (x.endsWith(".json")) => "json"
|
||||
case x if (x.endsWith(".jsp")) => "jsp"
|
||||
case x if (x.endsWith(".jsx")) => "jsx"
|
||||
case x if (x.endsWith(".cl")) => "lisp"
|
||||
case x if (x.endsWith(".clojure")) => "lisp"
|
||||
case x if (x.endsWith(".lua")) => "lua"
|
||||
case x if (x.endsWith(".php")) => "php"
|
||||
case x if (x.endsWith(".py")) => "python"
|
||||
case x if (x.endsWith(".rdoc")) => "rdoc"
|
||||
case x if (x.endsWith(".rhtml")) => "rhtml"
|
||||
case x if (x.endsWith(".ruby")) => "ruby"
|
||||
case x if (x.endsWith(".sh")) => "sh"
|
||||
case x if (x.endsWith(".sql")) => "sql"
|
||||
case x if (x.endsWith(".tcl")) => "tcl"
|
||||
case x if (x.endsWith(".vbs")) => "vbscript"
|
||||
case x if (x.endsWith(".yml")) => "yaml"
|
||||
case _ => "plain_text"
|
||||
}
|
||||
}
|
||||
|
||||
def pre(value: Html): Html = Html(s"<pre>${value.body.trim.split("\n").map(_.trim).mkString("\n")}</pre>")
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@(account: gitbucket.core.model.Account,
|
||||
groupNames: List[String],
|
||||
activities: List[gitbucket.core.model.Activity])(implicit context: gitbucket.core.controller.Context)
|
||||
activities: List[gitbucket.core.model.Activity],
|
||||
extraMailAddresses: List[String])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.account.html.main(account, groupNames, "activity"){
|
||||
@gitbucket.core.account.html.main(account, groupNames, "activity", extraMailAddresses){
|
||||
<div class="pull-right">
|
||||
<a href="@context.path/@{account.userName}.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(account: gitbucket.core.model.Account, groupNames: List[String], active: String,
|
||||
@(account: gitbucket.core.model.Account, groupNames: List[String], active: String, extraMailAddresses: List[String],
|
||||
isGroupManager: Boolean = false)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(account.userName){
|
||||
@@ -20,6 +20,16 @@
|
||||
<i class="octicon octicon-home"></i> <a href="@account.url">@account.url</a>
|
||||
</p>
|
||||
}
|
||||
@if(context.settings.showMailAddress){
|
||||
<p style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<i class="octicon octicon-mail"></i> <a href="mailto: @account.mailAddress">@account.mailAddress</a>
|
||||
</p>
|
||||
@extraMailAddresses.map{ mail =>
|
||||
<p style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
<i class="octicon octicon-mail"></i> <a href="mailto: @mail">@mail</a>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
<p style="color: #999">
|
||||
<i class="octicon octicon-clock"></i> Joined on @helpers.date(account.registeredDate)
|
||||
</p>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
@(account: gitbucket.core.model.Account, members: List[gitbucket.core.model.GroupMember], isGroupManager: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@(account: gitbucket.core.model.Account, members: List[gitbucket.core.model.GroupMember], extraMailAddresses: List[String], isGroupManager: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.account.html.main(account, Nil, "members", isGroupManager){
|
||||
@gitbucket.core.account.html.main(account, Nil, "members", extraMailAddresses, isGroupManager){
|
||||
@if(members.isEmpty){
|
||||
No members
|
||||
} else {
|
||||
@members.map { member =>
|
||||
<div class="block">
|
||||
<div class="block-header">
|
||||
@helpers.avatar(member.userName, 20) <a href="@helpers.url(member.userName)">@member.userName</a>
|
||||
@helpers.avatarLink(member.userName, 20) <a href="@helpers.url(member.userName)">@member.userName</a>
|
||||
@if(member.isManager){ (Manager) }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@(account: gitbucket.core.model.Account, groupNames: List[String],
|
||||
repositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
extraMailAddresses: List[String],
|
||||
isGroupManager: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.account.html.main(account, groupNames, "repositories", isGroupManager){
|
||||
@gitbucket.core.account.html.main(account, groupNames, "repositories", extraMailAddresses, isGroupManager){
|
||||
@if(repositories.isEmpty){
|
||||
No repositories
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<th>Property</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GITBUCKET_VERSION</td>
|
||||
<td>@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GITBUCKET_HOME</td>
|
||||
<td>@gitbucket.core.util.Directory.GitBucketHome</td>
|
||||
@@ -132,6 +136,21 @@
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- Show mail address -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
<label class="strong">Show mail address</label>
|
||||
<fieldset>
|
||||
<label class="radio">
|
||||
<input type="radio" name="showMailAddress" value="true"@if(context.settings.showMailAddress){ checked}>
|
||||
<span class="strong">Show</span> <span class="normal">- Anyone can view mail address by user's profile page.</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="showMailAddress" value="false"@if(!context.settings.showMailAddress){ checked}>
|
||||
<span class="strong">Hide</span> <span class="normal">- Hide mail address in user's profile page.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- Activity -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
}
|
||||
</div>
|
||||
<div class="strong">
|
||||
@helpers.avatar(account.userName, 20)
|
||||
@helpers.avatarLink(account.userName, 20)
|
||||
<a href="@helpers.url(account.userName)">@account.userName</a>
|
||||
@if(account.isGroupAccount){
|
||||
(Group)
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
@if(account.isGroupAccount){
|
||||
@members(account.userName).map { userName =>
|
||||
@helpers.avatar(userName, 20, tooltip = true)
|
||||
@helpers.avatarLink(userName, 20, tooltip = true)
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<div>
|
||||
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
|
||||
<div class="strong">
|
||||
@helpers.avatar(activity.activityUserName, 16)
|
||||
@helpers.avatarLink(activity.activityUserName, 16)
|
||||
@helpers.activityMessage(activity.message)
|
||||
</div>
|
||||
@activity.additionalInfo.map { additionalInfo =>
|
||||
@@ -83,7 +83,7 @@
|
||||
<div>
|
||||
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
|
||||
<div class="strong">
|
||||
@helpers.avatar(activity.activityUserName, 16)
|
||||
@helpers.avatarLink(activity.activityUserName, 16)
|
||||
@helpers.activityMessage(activity.message)
|
||||
</div>
|
||||
@additionalInfo
|
||||
@@ -97,7 +97,7 @@
|
||||
<div>
|
||||
<span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span>
|
||||
<div>
|
||||
@helpers.avatar(activity.activityUserName, 16)
|
||||
@helpers.avatarLink(activity.activityUserName, 16)
|
||||
@helpers.activityMessage(activity.message)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="commit-comment-@comment.commentId">
|
||||
<div class="markdown-body">
|
||||
<div>
|
||||
@helpers.avatar(comment.commentedUserName, 20)
|
||||
@helpers.avatarLink(comment.commentedUserName, 20)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">@gitbucket.core.helper.html.datetimeago(comment.registeredDate)</span>
|
||||
<span class="pull-right">
|
||||
@@ -27,7 +27,7 @@
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = false,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
</div>
|
||||
|
||||
@@ -57,6 +57,7 @@ $(function(){
|
||||
enableTaskList : @enableTaskList
|
||||
}, function(data){
|
||||
$('#preview-area@uid').html(data);
|
||||
$('#preview-area@uid input').prop('disabled', true);
|
||||
prettyPrint();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
comments: List[gitbucket.core.model.Comment],
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
pullreq: Option[gitbucket.core.model.PullRequest] = None)(implicit context: gitbucket.core.controller.Context)
|
||||
pullreq: Option[gitbucket.core.model.PullRequest] = None,
|
||||
commitId: Option[String] = None)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@issueOrPullRequest()={ @if(issue.exists(_.isPullRequest))( "pull request" )else( "issue" ) }
|
||||
@showFormattedComment(comment: gitbucket.core.model.IssueComment)={
|
||||
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
|
||||
<div class="panel-heading">
|
||||
@helpers.avatar(comment.commentedUserName, 20)
|
||||
@helpers.avatarLink(comment.commentedUserName, 20)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">
|
||||
@if(comment.action == "comment"){
|
||||
@@ -44,7 +45,7 @@
|
||||
@if(issue.isDefined){
|
||||
<div class="panel panel-default issue-comment-box">
|
||||
<div class="panel-heading">
|
||||
@helpers.avatar(issue.get.openedUserName, 20)
|
||||
@helpers.avatarLink(issue.get.openedUserName, 20)
|
||||
@helpers.user(issue.get.openedUserName, styleClass="username strong")
|
||||
<span class="muted">commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate)</span>
|
||||
<span class="pull-right">
|
||||
@@ -80,7 +81,7 @@
|
||||
<div class="discussion-item discussion-item-commit">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
added a commit that referenced this @issueOrPullRequest()
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -103,7 +104,7 @@
|
||||
<div class="discussion-item discussion-item-refer">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
referenced the @issueOrPullRequest()
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -120,7 +121,7 @@
|
||||
<div class="discussion-item discussion-item-merge">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-git-merge"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
merged commit
|
||||
<code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into
|
||||
@@ -140,7 +141,7 @@
|
||||
<div class="discussion-item discussion-item-close">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
closed this @issueOrPullRequest()
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -154,7 +155,7 @@
|
||||
<div class="discussion-item discussion-item-reopen">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-primitive-dot"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
reopened the @issueOrPullRequest()
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -165,7 +166,7 @@
|
||||
<div class="discussion-item discussion-item-delete_branch">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-git-branch"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
deleted the <code>@pullreq.map(_.requestBranch)</code> branch
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -176,7 +177,7 @@
|
||||
<div class="discussion-item discussion-item-add-label">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-tag"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
add the <code>@comment.content</code> label
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -187,7 +188,7 @@
|
||||
<div class="discussion-item discussion-item-delete-label">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-tag"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
removed the <code>@comment.content</code> label
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -198,7 +199,7 @@
|
||||
<div class="discussion-item discussion-item-change-priority">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-flame"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
change priority from <code>@comment.content.split(":")(0)</code> to <code>@comment.content.split(":")(1)</code>
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -209,7 +210,7 @@
|
||||
<div class="discussion-item discussion-item-change-milestone">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-milestone"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
change milestone from <code>@comment.content.split(":")(0)</code> to <code>@comment.content.split(":")(1)</code>
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
@@ -220,25 +221,36 @@
|
||||
<div class="discussion-item discussion-item-assign">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-person"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.avatarLink(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
change assignee from <code>@comment.content.split(":")(0)</code> to <code>@comment.content.split(":")(1)</code>
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
case "change_title" => {
|
||||
<div class="discussion-item discussion-item-pencil">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-pencil"></i></span>
|
||||
@helpers.avatar(comment.commentedUserName, 16)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
change title from <code>@comment.content.split("\r\n")(0)</code> to <code>@comment.content.split("\r\n")(1)</code>
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
case _ => {
|
||||
@showFormattedComment(comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
case comments: gitbucket.core.model.CommitComments => {
|
||||
@gitbucket.core.helper.html.commitcomments(comments, isManageable, repository, pullreq.map(_.commitIdTo))
|
||||
@gitbucket.core.helper.html.commitcomments(comments, isManageable, repository, commitId.orElse(pullreq.map(_.commitIdTo)))
|
||||
}
|
||||
case comment: gitbucket.core.model.CommitComment => {
|
||||
<div class="panel panel-default commit-comment-box commit-comment-@comment.commentId">
|
||||
<div class="panel-heading">
|
||||
@helpers.avatar(comment.commentedUserName, 20)
|
||||
@helpers.avatarLink(comment.commentedUserName, 20)
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">
|
||||
commented
|
||||
@@ -343,27 +355,6 @@ $(function(){
|
||||
return false;
|
||||
});
|
||||
|
||||
var extractMarkdown = function(data){
|
||||
$('body').append('<div id="tmp"></div>');
|
||||
$('#tmp').html(data);
|
||||
var markdown = $('#tmp textarea').val();
|
||||
$('#tmp').remove();
|
||||
return markdown;
|
||||
};
|
||||
|
||||
var replaceTaskList = function(issueContentHtml, checkboxes) {
|
||||
var ss = [],
|
||||
markdown = extractMarkdown(issueContentHtml),
|
||||
xs = markdown.split(/- \[[x| ]\]/g);
|
||||
for (var i=0; i<xs.length; i++) {
|
||||
ss.push(xs[i]);
|
||||
if (checkboxes.eq(i).prop('checked')) ss.push('- [x]');
|
||||
else ss.push('- [ ]');
|
||||
}
|
||||
ss.pop();
|
||||
return ss.join('');
|
||||
};
|
||||
|
||||
$('div[class*=commit-commentContent-]').on('click', ':checkbox', function(ev){
|
||||
var $commentContent = $(ev.target).parents('div[class*=commit-commentContent-]'),
|
||||
commentId = $commentContent.attr('class').match(/commit-commentContent-.+/)[0].replace(/commit-commentContent-/, ''),
|
||||
@@ -375,7 +366,7 @@ $(function(){
|
||||
type: 'POST',
|
||||
data: {
|
||||
issueId : 0,
|
||||
content : replaceTaskList(responseContent, checkboxes)
|
||||
content : applyTaskListCheckedStatus(responseContent, checkboxes)
|
||||
},
|
||||
success: function(data) {
|
||||
$('.commit-commentContent-' + commentId).html(data.content);
|
||||
@@ -395,7 +386,7 @@ $(function(){
|
||||
type: 'POST',
|
||||
data: {
|
||||
title : $('#issueTitle').text(),
|
||||
content : replaceTaskList(responseContent, checkboxes)
|
||||
content : applyTaskListCheckedStatus(responseContent, checkboxes)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -413,7 +404,7 @@ $(function(){
|
||||
type: 'POST',
|
||||
data: {
|
||||
issueId : 0,
|
||||
content : replaceTaskList(responseContent, checkboxes)
|
||||
content : applyTaskListCheckedStatus(responseContent, checkboxes)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
</div>
|
||||
<span id="label-assigned">
|
||||
@issue.flatMap(_.assignedUserName).map { userName =>
|
||||
@helpers.avatar(userName, 20) @helpers.user(userName, styleClass="username strong small")
|
||||
@helpers.avatarLink(userName, 20) @helpers.user(userName, styleClass="username strong small")
|
||||
}.getOrElse{
|
||||
<span class="muted small">No one</span>
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>@title</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>@title</title>
|
||||
<link rel="icon" href="@helpers.assets("/common/images/gitbucket.png")" type="image/vnd.microsoft.icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="@helpers.assets("/vendors/google-fonts/css/source-sans-pro.css")" rel="stylesheet">
|
||||
@@ -48,9 +49,10 @@
|
||||
<header class="main-header">
|
||||
<a href="@context.path/" class="logo">
|
||||
<span class="logo-mini"><img src="@helpers.assets("/common/images/gitbucket.svg")" alt="GitBucket" /></span>
|
||||
<span class="logo-lg"><img src="@helpers.assets("/common/images/gitbucket.svg")" alt="GitBucket" />
|
||||
<span class="header-title strong">GitBucket</span>
|
||||
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span></span>
|
||||
<span class="logo-lg">
|
||||
<img src="@helpers.assets("/common/images/gitbucket.svg")" alt="GitBucket" />
|
||||
<span class="header-title strong">GitBucket</span>
|
||||
</span>
|
||||
</a>
|
||||
<nav class="navbar navbar-static-top" role="navigation">
|
||||
<!-- Sidebar toggle button-->
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
pullreq: gitbucket.core.model.PullRequest,
|
||||
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||
comments: Seq[gitbucket.core.model.Comment],
|
||||
changedFileSize: Int,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.pulls.html.menu("commits", issue, pullreq, commits.flatten, comments, isManageable, repository){
|
||||
@gitbucket.core.pulls.html.menu("commits", issue, pullreq, commits.flatten, comments, changedFileSize, isManageable, repository){
|
||||
<table class="table table-bordered">
|
||||
@commits.map { day =>
|
||||
<tr>
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
<tr>
|
||||
<td style="width: 20%;">
|
||||
<i class="octicon octicon-git-commit"></i>
|
||||
@helpers.avatar(commit, 20)
|
||||
@helpers.avatarLink(commit, 20)
|
||||
@helpers.user(commit.authorName, commit.authorEmailAddress, "username strong")
|
||||
</td>
|
||||
<td><span class="monospace">@commit.shortMessage</span></td>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
pullreq: gitbucket.core.model.PullRequest,
|
||||
commits: Seq[gitbucket.core.util.JGitUtil.CommitInfo],
|
||||
comments: Seq[gitbucket.core.model.Comment],
|
||||
changedFileSize: Int,
|
||||
issueLabels: List[gitbucket.core.model.Label],
|
||||
collaborators: List[String],
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
@@ -14,7 +15,7 @@
|
||||
forkedRepository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
flash: Map[String, String])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.pulls.html.menu("conversation", issue, pullreq, commits, comments, isManageable, repository, flash){
|
||||
@gitbucket.core.pulls.html.menu("conversation", issue, pullreq, commits, comments, changedFileSize, isManageable, repository, flash){
|
||||
<link href="@helpers.assets("/vendors/jsdifflib/diffview.css")" type="text/css" rel="stylesheet" />
|
||||
<div class="col-md-9">
|
||||
<div id="comment-list">
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
comments: Seq[gitbucket.core.model.Comment],
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.pulls.html.menu("files", issue, pullreq, commits, comments, isManageable, repository) {
|
||||
@gitbucket.core.pulls.html.menu("files", issue, pullreq, commits, comments, diffs.size, isManageable, repository) {
|
||||
@gitbucket.core.helper.html.diff(
|
||||
diffs,
|
||||
repository,
|
||||
commits.headOption.map(_.id),
|
||||
commits.lastOption.map(_.id),
|
||||
Some(pullreq.commitIdFrom),
|
||||
true,
|
||||
Some(pullreq.issueId),
|
||||
isManageable,
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
pullreq: gitbucket.core.model.PullRequest,
|
||||
commits: Seq[gitbucket.core.util.JGitUtil.CommitInfo],
|
||||
comments: Seq[gitbucket.core.model.Comment],
|
||||
changedFileSize: Int,
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
flash: Map[String, String] = Map.empty)(body: => Html)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.model.Comment
|
||||
@import gitbucket.core.model.CommitComments
|
||||
@import gitbucket.core.model.IssueComment
|
||||
@gitbucket.core.html.main(s"${issue.title} - Pull request #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@gitbucket.core.html.menu("pulls", repository) {
|
||||
@@ -68,9 +71,9 @@
|
||||
}
|
||||
</div>
|
||||
<ul class="nav nav-tabs fill-width" id="pullreq-tab">
|
||||
<li @if(active=="conversation"){class="active"}><a href="@helpers.url(repository)/pull/@issue.issueId">Conversation</a></li>
|
||||
<li @if(active=="commits"){class="active"}><a href="@helpers.url(repository)/pull/@issue.issueId/commits">Commits</a></li>
|
||||
<li @if(active=="files"){class="active"}><a href="@helpers.url(repository)/pull/@issue.issueId/files">Files Changed</a></li>
|
||||
<li @if(active=="conversation"){class="active"}><a href="@helpers.url(repository)/pull/@issue.issueId">Conversation <span class="badge">@countConversation(comments)</span></a></li>
|
||||
<li @if(active=="commits"){class="active"}><a href="@helpers.url(repository)/pull/@issue.issueId/commits">Commits <span class="badge">@commits.size</span></a></li>
|
||||
<li @if(active=="files"){class="active"}><a href="@helpers.url(repository)/pull/@issue.issueId/files">Files Changed <span class="badge">@changedFileSize</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content fill-width" style="padding-top: 20px;">
|
||||
@flash.get("error").map { error =>
|
||||
@@ -83,3 +86,44 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#edit').click(function(){
|
||||
$('.edit-title').show();
|
||||
$('.show-title').hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#update').click(function(){
|
||||
$(this).attr('disabled', 'disabled');
|
||||
$.ajax({
|
||||
url: '@helpers.url(repository)/issues/edit_title/@issue.issueId',
|
||||
type: 'POST',
|
||||
data: {
|
||||
title : $('#edit-title').val()
|
||||
}
|
||||
}).done(function(data){
|
||||
$('#show-title').empty().text(data.title);
|
||||
$('#cancel').click();
|
||||
$(this).removeAttr('disabled');
|
||||
}).fail(function(req){
|
||||
$(this).removeAttr('disabled');
|
||||
$('#error-edit-title').text($.parseJSON(req.responseText).title);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#cancel').click(function(){
|
||||
$('.edit-title').hide();
|
||||
$('.show-title').show();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@countConversation(comments: Seq[Comment]) = @{
|
||||
comments.collect {
|
||||
case c: CommitComments => c.comments.size
|
||||
case c: IssueComment if c.action.endsWith("comment") => 1
|
||||
}.sum
|
||||
}
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
@(issue: gitbucket.core.model.Issue,
|
||||
pullreq: gitbucket.core.model.PullRequest,
|
||||
comments: List[gitbucket.core.model.Comment],
|
||||
issueLabels: List[gitbucket.core.model.Label],
|
||||
collaborators: List[String],
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean,
|
||||
isManageableForkedRepository: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
forkedRepository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
flash: Map[String, String])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.model.IssueComment
|
||||
@import gitbucket.core.model.CommitComment
|
||||
@*
|
||||
@gitbucket.core.html.main(s"${issue.title} - Pull request #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("pulls", repository){
|
||||
@defining(dayByDayCommits.flatten){ commits =>
|
||||
<div>
|
||||
<div class="show-title pull-right">
|
||||
@if(isManageable || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
<a class="btn btn-default" href="#" id="edit">Edit</a>
|
||||
}
|
||||
@if(context.loginAccount.isDefined){
|
||||
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
|
||||
}
|
||||
</div>
|
||||
<div class="edit-title pull-right" style="display: none;">
|
||||
<a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
|
||||
</div>
|
||||
<h1 class="body-title">
|
||||
<span class="show-title">
|
||||
<span id="show-title">@issue.title</span>
|
||||
<span class="muted">#@issue.issueId</span>
|
||||
</span>
|
||||
<span class="edit-title" style="display: none;">
|
||||
<span id="error-edit-title" class="error"></span>
|
||||
<input type="text" class="form-control" style="width: 700px;" id="edit-title" value="@issue.title"/>
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div style="margin-bottom: 15px">
|
||||
@if(issue.closed) {
|
||||
@comments.flatMap @{
|
||||
case comment: IssueComment => Some(comment)
|
||||
case _ => None
|
||||
}.find(_.action == "merge").map{ comment =>
|
||||
<span class="label label-info issue-status">Merged</span>
|
||||
<span class="muted">
|
||||
@helpers.user(comment.commentedUserName, styleClass="username strong") merged @commits.size @helpers.plural(commits.size, "commit")
|
||||
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
</span>
|
||||
}.getOrElse {
|
||||
<span class="label label-important issue-status">Closed</span>
|
||||
<span class="muted">
|
||||
@helpers.user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @helpers.plural(commits.size, "commit")
|
||||
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
||||
</span>
|
||||
}
|
||||
} else {
|
||||
<span class="label label-success issue-status">Open</span>
|
||||
<span class="muted">
|
||||
@helpers.user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @helpers.plural(commits.size, "commit")
|
||||
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<ul class="nav nav-tabs fill-width" id="pullreq-tab">
|
||||
<li><a href="#conversation">Conversation <span class="badge">@comments.flatMap @{
|
||||
case comment: IssueComment => Some(comment)
|
||||
case _ => None
|
||||
}.size</span></a></li>
|
||||
<li><a href="#commits">Commits <span class="badge">@commits.size</span></a></li>
|
||||
<li><a href="#files">Files Changed <span class="badge">@diffs.size</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content fill-width" style="padding-top: 20px;">
|
||||
<div class="tab-pane" id="conversation">
|
||||
@flash.get("error").map{ error =>
|
||||
<div class="alert alert-error">@error</div>
|
||||
}
|
||||
@flash.get("info").map{ info =>
|
||||
<div class="alert alert-info">@info</div>
|
||||
}
|
||||
@gitbucket.core.pulls.html.conversation(issue, pullreq, commits, comments, issueLabels, collaborators, milestones, priorities, labels, isEditable, isManageable, isManageableForkedRepository, repository, forkedRepository)
|
||||
</div>
|
||||
<div class="tab-pane" id="commits">
|
||||
@if(commits.nonEmpty){
|
||||
@gitbucket.core.pulls.html.commits(dayByDayCommits, repository)
|
||||
}
|
||||
</div>
|
||||
<div class="tab-pane" id="files">
|
||||
@if(commits.nonEmpty){
|
||||
@gitbucket.core.helper.html.diff(diffs, repository, commits.headOption.map(_.id), commits.lastOption.map(_.id), true, Some(pullreq.issueId), isManageable, true)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
// Determine active tab from hash
|
||||
if(location.hash == '#commits'){
|
||||
$('li:has(a[href="#commits"])').addClass('active');
|
||||
$('div#commits').addClass('active');
|
||||
} else if(location.hash == '#files'){
|
||||
$('li:has(a[href="#files"])').addClass('active');
|
||||
$('div#files').addClass('active');
|
||||
} else {
|
||||
$('li:has(a[href="#conversation"])').addClass('active');
|
||||
$('div#conversation').addClass('active');
|
||||
}
|
||||
// Set hash when tab is clicked
|
||||
$('ul.nav-tabs li a').click(function(e){
|
||||
location.href = $(e.delegateTarget).attr("href");
|
||||
});
|
||||
|
||||
$('#pullreq-tab a').click(function (e) {
|
||||
e.preventDefault();
|
||||
$(this).tab('show');
|
||||
});
|
||||
|
||||
$('#edit').click(function(){
|
||||
$('.edit-title').show();
|
||||
$('.show-title').hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#update').click(function(){
|
||||
$(this).attr('disabled', 'disabled');
|
||||
$.ajax({
|
||||
url: '@helpers.url(repository)/issues/edit_title/@issue.issueId',
|
||||
type: 'POST',
|
||||
data: {
|
||||
title : $('#edit-title').val()
|
||||
}
|
||||
}).done(function(data){
|
||||
$('#show-title').empty().text(data.title);
|
||||
$('#cancel').click();
|
||||
$(this).removeAttr('disabled');
|
||||
}).fail(function(req){
|
||||
$(this).removeAttr('disabled');
|
||||
$('#error-edit-title').text($.parseJSON(req.responseText).title);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#cancel').click(function(){
|
||||
$('.edit-title').hide();
|
||||
$('.show-title').show();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
*@
|
||||
@@ -22,7 +22,7 @@
|
||||
@release.map { case (release, assets) =>
|
||||
<h3><a href="@helpers.url(repository)/releases/@release.tag">@release.name</a></h3>
|
||||
<p class="muted">
|
||||
@helpers.avatar(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
|
||||
@helpers.avatarLink(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
|
||||
</p>
|
||||
@helpers.markdown(
|
||||
markdown = release.content getOrElse "No description provided.",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
</h3>
|
||||
<p class="muted">
|
||||
@helpers.avatar(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
|
||||
@helpers.avatarLink(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
|
||||
</p>
|
||||
@helpers.markdown(
|
||||
markdown = release.content getOrElse "No description provided.",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
}
|
||||
</div>
|
||||
<div class="box-header">
|
||||
@helpers.avatar(latestCommit, 28)
|
||||
@helpers.avatarLink(latestCommit, 28)
|
||||
@helpers.user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
|
||||
<span class="muted">@gitbucket.core.helper.html.datetimeago(latestCommit.commitTime)</span>
|
||||
<span class="label label-default">@helpers.readableSize(content.size)</span>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
newLineNumber: Option[Int] = None,
|
||||
issueId: Option[Int] = None,
|
||||
hasWritePermission: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
uid: Long = new java.util.Date().getTime())(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@if(context.loginAccount.isDefined){
|
||||
@if(!fileName.isDefined){<hr/><br/>}
|
||||
@@ -21,13 +22,14 @@
|
||||
hasWritePermission = hasWritePermission,
|
||||
completionContext = "issues",
|
||||
style = "height: 100px; max-height: 150px;",
|
||||
elastic = true
|
||||
elastic = true,
|
||||
uid = uid
|
||||
)
|
||||
</div>
|
||||
@if(fileName.isDefined){
|
||||
<div class="pull-right" style="margin-top: 10px;">
|
||||
<input type="button" class="btn btn-default" value="Cancel"/>
|
||||
<input type="submit" class="btn btn-success btn-inline-comment" formaction="@helpers.url(repository)/commit/@commitId/comment/new" value="Comment"/>
|
||||
<input type="submit" id="btn-inline-comment-@uid" class="btn btn-success" formaction="@helpers.url(repository)/commit/@commitId/comment/new" value="Comment"/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -42,7 +44,7 @@
|
||||
@newLineNumber.map { newLineNumber => <input type="hidden" name="newLineNumber" value="@newLineNumber"> }
|
||||
</form>
|
||||
<script>
|
||||
$('.btn-inline-comment').click(function(e) {
|
||||
$('#btn-inline-comment-@uid').click(function(e) {
|
||||
e.preventDefault();
|
||||
var $form = $(e.target).attr('disabled', 'disabled').closest('form');
|
||||
var param = {};
|
||||
@@ -76,7 +78,28 @@
|
||||
var $tr = $form.closest('tr.not-diff');
|
||||
|
||||
// Apply comment
|
||||
$tr.removeClass('inline-comment-form').html(tmp).find('.comment-box-container').html(data);
|
||||
var $addedCommentContent = $tr.removeClass('inline-comment-form').html(tmp).find('.comment-box-container');
|
||||
$addedCommentContent.html(data);
|
||||
$addedCommentContent.find('div[class*=commit-commentContent-]').on('click', ':checkbox', function(ev){
|
||||
var $commentContent = $(ev.target).parents('div[class*=commit-commentContent-]'),
|
||||
commentId = $commentContent.attr('class').match(/commit-commentContent-.+/)[0].replace(/commit-commentContent-/, ''),
|
||||
checkboxes = $commentContent.find(':checkbox');
|
||||
$.get('@helpers.url(repository)/commit_comments/_data/' + commentId, { dataType : 'html' },
|
||||
function(responseContent){
|
||||
$.ajax({
|
||||
url: '@helpers.url(repository)/commit_comments/edit/' + commentId,
|
||||
type: 'POST',
|
||||
data: {
|
||||
issueId : 0,
|
||||
content : applyTaskListCheckedStatus(responseContent, checkboxes)
|
||||
},
|
||||
success: function(data) {
|
||||
$('.commit-commentContent-' + commentId).html(data.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Show reply comment form
|
||||
var replyComment = $tr.prev().find('.reply-comment').closest('.not-diff').show();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.view.helpers.RichHtmlSeq
|
||||
@import gitbucket.core.model._
|
||||
@gitbucket.core.html.main(commit.shortMessage, Some(repository)){
|
||||
@gitbucket.core.html.menu("files", repository){
|
||||
<table class="table table-bordered">
|
||||
@@ -68,7 +69,7 @@
|
||||
|
||||
<div class="author-info">
|
||||
<div class="author">
|
||||
@helpers.avatar(commit, 20)
|
||||
@helpers.avatarLink(commit, 20)
|
||||
<span>@helpers.user(commit.authorName, commit.authorEmailAddress, "username strong")</span>
|
||||
<span class="muted">authored @gitbucket.core.helper.html.datetimeago(commit.authorTime)</span>
|
||||
</div>
|
||||
@@ -97,10 +98,19 @@
|
||||
<div style="display: none;">
|
||||
@gitbucket.core.issues.html.commentlist(
|
||||
None,
|
||||
comments.filter(_.asInstanceOf[gitbucket.core.model.CommitComment].fileName.isDefined),
|
||||
comments.map(_.asInstanceOf[CommitComment]).filter(_.fileName.isDefined).groupBy(_.fileName).map { case (fileName, comments) =>
|
||||
CommitComments(
|
||||
fileName = fileName.get,
|
||||
commentedUserName = comments.head.commentedUserName,
|
||||
registeredDate = comments.head.registeredDate,
|
||||
comments = comments,
|
||||
diff = None
|
||||
)
|
||||
}.toList,
|
||||
hasWritePermission,
|
||||
repository,
|
||||
None)
|
||||
None,
|
||||
Some(commitId))
|
||||
</div>
|
||||
</div>
|
||||
@gitbucket.core.repo.html.commentform(commitId = commitId, hasWritePermission = hasWritePermission, repository = repository)
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<input type="text" name="message" class="form-control"/>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<a href="@helpers.url(repository)/blob/@helpers.encodeRefName((branch :: pathList).mkString("/"))" class="btn btn-danger">Cancel</a>
|
||||
<a href="@helpers.url(repository)/blob/@helpers.encodeRefName((branch :: pathList).mkString("/"))" class="btn btn-default">Cancel</a>
|
||||
<input type="submit" id="commitButton" class="btn btn-success" value="Commit changes"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
@if(fileName.isEmpty){
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName((branch :: pathList).mkString("/"))" class="btn btn-default">Cancel</a>
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/blob/@helpers.encodeRefName((branch :: pathList ++ Seq(fileName.get)).mkString("/"))" class="btn btn-danger">Cancel</a>
|
||||
<a href="@helpers.url(repository)/blob/@helpers.encodeRefName((branch :: pathList ++ Seq(fileName.get)).mkString("/"))" class="btn btn-default">Cancel</a>
|
||||
}
|
||||
<input type="submit" id="commitButton" class="btn btn-success" value="Commit changes" disabled="true"/>
|
||||
<input type="hidden" id="charset" name="charset" value="@content.charset"/>
|
||||
@@ -75,6 +75,7 @@
|
||||
}
|
||||
}
|
||||
<script src="@helpers.assets("/vendors/ace/ace.js")" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="@helpers.assets("/vendors/ace/ext-modelist.js")" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="@helpers.assets("/vendors/jsdifflib/difflib.js")"></script>
|
||||
<link href="@helpers.assets("/vendors/jsdifflib/diffview.css")" type="text/css" rel="stylesheet" />
|
||||
<script>
|
||||
@@ -89,7 +90,9 @@ $(function(){
|
||||
}
|
||||
|
||||
@if(fileName.isDefined){
|
||||
editor.getSession().setMode("ace/mode/@helpers.editorType(fileName.get)");
|
||||
var modelist = ace.require("ace/ext/modelist");
|
||||
var mode = modelist.getModeForPath("@fileName.get");
|
||||
editor.getSession().setMode(mode.mode);
|
||||
}
|
||||
@if(protectedBranch){
|
||||
editor.setReadOnly(true);
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
<div class="head" style="height: 24px;">
|
||||
<div class="pull-right">
|
||||
<div class="btn-group">
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName((latestCommit.id :: pathList).mkString("/"))" data-hotkey="y" style="display: none;">Transfer to URL with SHA</a>
|
||||
<a href="@{helpers.url(repository)}/archive/@{helpers.encodeRefName(branch)}@if(pathList.length > 0){/@pathList.mkString("/")}/@{helpers.encodeRefName(branch)}.zip" class="btn btn-sm btn-default pc"><i class="octicon octicon-cloud-download"></i> Download ZIP</a>
|
||||
<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(commitCount > 10000){10000+} else {@commitCount} @helpers.plural(commitCount, "commit")</a>
|
||||
</div>
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName((latestCommit.id :: pathList).mkString("/"))" data-hotkey="y" style="display: none;">Transfer to URL with SHA</a>
|
||||
</div>
|
||||
@if(pathList.isEmpty){
|
||||
<div class="pull-right pc" style="margin-right: 5px;">
|
||||
@@ -44,7 +45,6 @@
|
||||
@if(context.platform != "linux" && context.platform != null){
|
||||
<a href="@RepositoryService.openRepoUrl(repository.httpUrl)" id="repository-clone-url" class="btn btn-sm btn-default"><i class="octicon octicon-desktop-download"></i></a>
|
||||
}
|
||||
<a href="@{helpers.url(repository)}/archive/@{helpers.encodeRefName(branch)}.zip" class="btn btn-sm btn-default"><i class="octicon octicon-cloud-download"></i> Download ZIP</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right pc">
|
||||
@@ -116,84 +116,84 @@
|
||||
</div>
|
||||
<div class="author-info">
|
||||
<div class="author">
|
||||
@helpers.avatar(latestCommit, 20)
|
||||
@helpers.avatarLink(latestCommit, 20)
|
||||
<span>@helpers.user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")</span>
|
||||
<span class="muted"> authored @gitbucket.core.helper.html.datetimeago(latestCommit.authorTime)</span>
|
||||
</div>
|
||||
@if(latestCommit.isDifferentFromAuthor) {
|
||||
<div class="committer">
|
||||
<span class="octicon octicon-arrow-right"></span>
|
||||
<span>@helpers.user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong")</span>
|
||||
<span class="muted"> committed @gitbucket.core.helper.html.datetimeago(latestCommit.commitTime)</span>
|
||||
</div>
|
||||
<div class="committer">
|
||||
<span class="octicon octicon-arrow-right"></span>
|
||||
<span>@helpers.user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong")</span>
|
||||
<span class="muted"> committed @gitbucket.core.helper.html.datetimeago(latestCommit.commitTime)</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@if(pathList.size > 0){
|
||||
<tr>
|
||||
<td width="16" class="file-icon"></td>
|
||||
<td><a href="@helpers.url(repository)@if(pathList.size > 1){/tree/@helpers.encodeRefName(branch)/@helpers.encodeRefName(pathList.init.mkString("/"))}">..</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="16" class="file-icon"></td>
|
||||
<td><a href="@helpers.url(repository)@if(pathList.size > 1){/tree/@helpers.encodeRefName(branch)/@helpers.encodeRefName(pathList.init.mkString("/"))}">..</a></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
}
|
||||
@files.map { file =>
|
||||
<tr>
|
||||
<td width="16" class="file-icon">
|
||||
@if(file.isDirectory){
|
||||
@if(file.linkUrl.isDefined){
|
||||
<i class="octicon octicon-file-symlink-directory"></i>
|
||||
} else {
|
||||
<i class="octicon octicon-file-directory"></i>
|
||||
}
|
||||
} else {
|
||||
<i class="octicon octicon-file-text"></i>
|
||||
}
|
||||
</td>
|
||||
<td class="ellipsis-cell" style="width: 20%; min-width: 160px;">
|
||||
@if(file.isDirectory){
|
||||
@{file.linkUrl match {
|
||||
case Some(linkUrl) if linkUrl.startsWith("http://") || linkUrl.startsWith("https://") => {
|
||||
<a href={linkUrl}>
|
||||
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||
case Nil => ""
|
||||
case list => list.mkString("", "/", "/")
|
||||
}}</span>
|
||||
{file.name.split("/").toList.last}
|
||||
</a>
|
||||
<tr>
|
||||
<td width="16" class="file-icon">
|
||||
@if(file.isDirectory){
|
||||
@if(file.linkUrl.isDefined){
|
||||
<i class="octicon octicon-file-symlink-directory"></i>
|
||||
} else {
|
||||
<i class="octicon octicon-file-directory"></i>
|
||||
}
|
||||
case Some(_) => {
|
||||
<span>
|
||||
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||
case Nil => ""
|
||||
case list => list.mkString("", "/", "/")
|
||||
}}</span>
|
||||
{file.name.split("/").toList.last}
|
||||
</span>
|
||||
}
|
||||
case None => {
|
||||
<a href={helpers.url(repository) + "/tree" + helpers.encodeRefName((branch :: pathList).mkString("/", "/", "/") + file.name)}>
|
||||
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||
case Nil => ""
|
||||
case list => list.mkString("", "/", "/")
|
||||
}}</span>
|
||||
{file.name.split("/").toList.last}
|
||||
</a>
|
||||
}
|
||||
}}
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/blob@{helpers.encodeRefName((branch :: pathList).mkString("/", "/", "/") + file.name)}">@file.name</a>
|
||||
}
|
||||
</td>
|
||||
<td class="ellipsis-cell" style="width: 70%;">
|
||||
<span>
|
||||
<a href="@helpers.url(repository)/commit/@file.commitId" class="commit-message" title="@file.message">@helpers.link(file.message, repository)</a>
|
||||
</span>
|
||||
</td>
|
||||
<td style="width: 10%; min-width: 125px; text-align: right;">@gitbucket.core.helper.html.datetimeago(file.time, false)</td>
|
||||
</tr>
|
||||
} else {
|
||||
<i class="octicon octicon-file-text"></i>
|
||||
}
|
||||
</td>
|
||||
<td class="ellipsis-cell" style="width: 20%; min-width: 160px;">
|
||||
@if(file.isDirectory){
|
||||
@{file.linkUrl match {
|
||||
case Some(linkUrl) if linkUrl.startsWith("http://") || linkUrl.startsWith("https://") => {
|
||||
<a href={linkUrl}>
|
||||
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||
case Nil => ""
|
||||
case list => list.mkString("", "/", "/")
|
||||
}}</span>
|
||||
{file.name.split("/").toList.last}
|
||||
</a>
|
||||
}
|
||||
case Some(_) => {
|
||||
<span>
|
||||
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||
case Nil => ""
|
||||
case list => list.mkString("", "/", "/")
|
||||
}}</span>
|
||||
{file.name.split("/").toList.last}
|
||||
</span>
|
||||
}
|
||||
case None => {
|
||||
<a href={helpers.url(repository) + "/tree" + helpers.encodeRefName((branch :: pathList).mkString("/", "/", "/") + file.name)}>
|
||||
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||
case Nil => ""
|
||||
case list => list.mkString("", "/", "/")
|
||||
}}</span>
|
||||
{file.name.split("/").toList.last}
|
||||
</a>
|
||||
}
|
||||
}}
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/blob@{helpers.encodeRefName((branch :: pathList).mkString("/", "/", "/") + file.name)}">@file.name</a>
|
||||
}
|
||||
</td>
|
||||
<td class="ellipsis-cell" style="width: 70%;">
|
||||
<span>
|
||||
<a href="@helpers.url(repository)/commit/@file.commitId" class="commit-message" title="@file.message">@helpers.link(file.message, repository)</a>
|
||||
</span>
|
||||
</td>
|
||||
<td style="width: 10%; min-width: 125px; text-align: right;">@gitbucket.core.helper.html.datetimeago(file.time, false)</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
@readme.map { case(filePath, content) =>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
<div class="block">
|
||||
@if(originRepository.isDefined){
|
||||
@helpers.avatar(originRepository.get.owner, 20)
|
||||
@helpers.avatarLink(originRepository.get.owner, 20)
|
||||
<span@if(repository.owner == originRepository.get.owner){ class="highlight"}>
|
||||
<a href="@helpers.url(originRepository.get.owner)">@originRepository.get.owner</a> / <a href="@context.path/@originRepository.get.owner/@originRepository.get.name">@originRepository.get.name</a>
|
||||
</span>
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
@members.map { case (owner, name) =>
|
||||
<div class="block">
|
||||
@helpers.avatar(owner, 20)
|
||||
@helpers.avatarLink(owner, 20)
|
||||
<span@if(repository.owner == owner){ class="highlight"}>
|
||||
<a href="@helpers.url(owner)">@owner</a> / <a href="@context.path/@owner/@name">@name</a>
|
||||
</span>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<input type="text" name="message" class="form-control"/>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch + "/" + pathList.mkString("/"))" class="btn btn-danger">Cancel</a>
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch + "/" + pathList.mkString("/"))" class="btn btn-default">Cancel</a>
|
||||
<input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
|
||||
<input type="hidden" id="upload-files-data" name="uploadFiles" value=""/>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
@commits.map { commit =>
|
||||
<tr>
|
||||
<td style="width: 32px; text-align: center ;"><input type="checkbox" name="commitId" value="@commit.id"></td>
|
||||
<td style="width: 200px;">@helpers.avatar(commit, 20) @helpers.user(commit.authorName, commit.authorEmailAddress)</td>
|
||||
<td style="width: 200px;">@helpers.avatarLink(commit, 20) @helpers.user(commit.authorName, commit.authorEmailAddress)</td>
|
||||
<td>
|
||||
<span class="muted">@gitbucket.core.helper.html.datetimeago(commit.authorTime):</span> @commit.shortMessage
|
||||
</td>
|
||||
@@ -70,4 +70,4 @@
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,10 +138,6 @@ div.content-wrapper {
|
||||
/* ======================================================================== */
|
||||
/* Global Header */
|
||||
/* ======================================================================== */
|
||||
span.header-version {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.main-header .logo img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
@@ -78,10 +78,10 @@ function displayErrors(data, elem){
|
||||
function diffUsingJS(oldTextId, newTextId, outputId, viewType, ignoreSpace) {
|
||||
var old = $('#'+oldTextId), head = $('#'+newTextId);
|
||||
var render = new JsDiffRender({
|
||||
oldText: old.data('val'),
|
||||
oldTextName: old.data('file-name'),
|
||||
newText: head.data('val'),
|
||||
newTextName: head.data('file-name'),
|
||||
oldText: old.attr('data-val'),
|
||||
oldTextName: old.attr('data-file-name'),
|
||||
newText: head.attr('data-val'),
|
||||
newTextName: head.attr('data-file-name'),
|
||||
ignoreSpace: ignoreSpace,
|
||||
contextSize: 4
|
||||
});
|
||||
@@ -726,3 +726,35 @@ function checkExtraMailAddress(){
|
||||
$(this).remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* function for extracting markdown from comment area.
|
||||
* @param commentArea a comment area
|
||||
* @returns {*|jQuery}
|
||||
*/
|
||||
var extractMarkdown = function(commentArea){
|
||||
$('body').append('<div id="tmp"></div>');
|
||||
$('#tmp').html(commentArea);
|
||||
var markdown = $('#tmp textarea').val();
|
||||
$('#tmp').remove();
|
||||
return markdown;
|
||||
};
|
||||
|
||||
/**
|
||||
* function for applying checkboxes status of task list.
|
||||
* @param commentArea a comment area
|
||||
* @param checkboxes checkboxes for task list
|
||||
* @returns {string} a markdown that applied checkbox status
|
||||
*/
|
||||
var applyTaskListCheckedStatus = function(commentArea, checkboxes) {
|
||||
var ss = [],
|
||||
markdown = extractMarkdown(commentArea),
|
||||
xs = markdown.split(/- \[[x| ]\]/g);
|
||||
for (var i=0; i<xs.length; i++) {
|
||||
ss.push(xs[i]);
|
||||
if (checkboxes.eq(i).prop('checked')) ss.push('- [x]');
|
||||
else ss.push('- [ ]');
|
||||
}
|
||||
ss.pop();
|
||||
return ss.join('');
|
||||
};
|
||||
|
||||
@@ -134,7 +134,8 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
|
||||
ldap = None,
|
||||
oidcAuthentication = false,
|
||||
oidc = None,
|
||||
skinName = "skin-blue"
|
||||
skinName = "skin-blue",
|
||||
showMailAddress = false
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user