Compare commits

...

79 Commits

Author SHA1 Message Date
Naoki Takezoe
165cf88219 Bump gist-plugin to 4.15.0 which supports GitBucket 4.25.0 2018-05-29 02:09:19 +09:00
Naoki Takezoe
80b7e15d94 Bump to 4.15.0 2018-05-29 00:04:02 +09:00
Naoki Takezoe
6eadebede2 Merge pull request #2035 from kazuki43zoo/gh-2015_count-conversations
Count multiple comments on the same line
2018-05-28 23:54:25 +09:00
Kazuki Shimizu
beb0401500 Count multiple comments on the same line
See #2015
2018-05-28 22:58:34 +09:00
Naoki Takezoe
eface25cf8 Update README and CHANGELOG 2018-05-28 15:56:36 +09:00
Naoki Takezoe
9eff4cb485 (refs #2031) Fix permission check for the repository menu 2018-05-28 15:49:47 +09:00
Naoki Takezoe
c65c3e2c49 Escape HTML 2018-05-28 15:42:32 +09:00
Naoki Takezoe
d064ca85fb Encode parameters in url generation helpers 2018-05-28 12:30:07 +09:00
Naoki Takezoe
df9c34bcec Fix indent 2018-05-28 12:26:11 +09:00
Naoki Takezoe
172701105a URL encode username in user link 2018-05-28 12:15:26 +09:00
Naoki Takezoe
e2da18a763 (refs #2030) Fix isolation level issue in MySQL 2018-05-28 00:46:53 +09:00
Naoki Takezoe
77383c4e8f Merge pull request #2034 from gitbucket/extra-mail-address-migration
Migration to delete empty extra mail addresses
2018-05-28 00:45:02 +09:00
Naoki Takezoe
aba9db3857 Migration to delete empty extra mail addresses 2018-05-27 22:46:50 +09:00
Naoki Takezoe
d97677aaaa Merge branch 'ThaFridge-master' 2018-05-27 13:14:10 +09:00
Naoki Takezoe
d02a4baf47 Remove duplicated X-UA-Compatible meta tag 2018-05-27 13:13:51 +09:00
ThaFridge
4ce07ee3dd Update with propper mobile/tablet scalling 2018-05-27 13:11:52 +09:00
Naoki Takezoe
a354522406 Merge pull request #2032 from kounoike/pr-fix-extra-email-bug
fix extra email bug in account registration
2018-05-27 12:31:42 +09:00
Naoki Takezoe
c4bdf86253 Merge pull request #2015 from kazuki43zoo/count-conversation
Counting conversations on pull request screen
2018-05-27 03:36:45 +09:00
KOUNOIKE Yuusuke
87fa283b65 fix extra email bug in account registration 2018-05-26 12:58:31 +09:00
Kazuki Shimizu
71828e5d08 Counting conversations on pull request screen 2018-05-26 09:51:56 +09:00
Naoki Takezoe
89bf8db087 Add new line to the end of response 2018-05-25 02:07:13 +09:00
Naoki Takezoe
2b2669978f Merge pull request #2027 from gitbucket/fix-download
Fix for repository downloading improvement
2018-05-24 18:20:30 +09:00
Naoki Takezoe
e7493eff3b Merge pull request #2028 from yaroot/pubkey
expose user pubkey via /{user}.keys
2018-05-24 16:29:47 +09:00
Naoki Takezoe
1adb0b7bcf Include repository name and directory name in download filename 2018-05-24 16:25:43 +09:00
Naoki Takezoe
587970a477 Merge branch 'master' into fix-download 2018-05-24 16:02:16 +09:00
Naoki Takezoe
b45e6428c7 Merge pull request #2029 from gitbucket/revert-2026-pr-show-edited
Revert "show "edited" in edited comment"
2018-05-24 11:19:38 +09:00
Naoki Takezoe
7c758cbdee Revert "show "edited" in edited comment" 2018-05-24 11:03:41 +09:00
Naoki Takezoe
ffc0b59a58 Merge pull request #2026 from kounoike/pr-show-edited
show "edited" in edited comment
2018-05-24 10:48:22 +09:00
Naoki Takezoe
382250c243 Merge pull request #2017 from kounoike/pr-secure-password
use PBKDF2 for password
2018-05-24 10:16:31 +09:00
Yan Su
489ba2cd17 expose user pubkey via /{user}.keys 2018-05-24 08:30:02 +08:00
Naoki Takezoe
2a489870a1 Fix for repository downloading improvement in #2014 2018-05-24 01:25:07 +09:00
KOUNOIKE Yuusuke
99f1eaf3d8 fix by review comment 2018-05-24 00:30:13 +09:00
KOUNOIKE Yuusuke
e1c7cd0965 show "edited" in edited comment 2018-05-24 00:29:30 +09:00
Naoki Takezoe
fbe60a59d7 Merge pull request #2025 from uli-heller/mariadb-2.2.4
mariadb-java-client: 2.2.3 -> 2.2.4
2018-05-24 00:23:59 +09:00
Naoki Takezoe
efdf27df6b Merge pull request #2014 from kounoike/pr-archive-improve
improve archive download
2018-05-24 00:10:24 +09:00
KOUNOIKE Yuusuke
9ffda21bfd fix by review comment 2018-05-23 23:37:10 +09:00
Uli Heller
8ee7270986 mariadb-java-client: 2.2.3 -> 2.2.4 2018-05-23 08:16:36 +02:00
Naoki Takezoe
d95a6b8134 (refs #2023)Move GitBucket version from global header to admin page 2018-05-23 14:51:06 +09:00
Naoki Takezoe
31d546fd5a Merge pull request #2016 from kazuki43zoo/create-comment-on-change-title
Create comment on changing issue title
2018-05-23 02:18:28 +09:00
Naoki Takezoe
9812f66b0d Merge pull request #2013 from kazuki43zoo/avatarLink
Change to avatar image link from avatar image
2018-05-23 02:17:46 +09:00
Kazuki Shimizu
5ac8b87a76 Update 'About Action in Issue Comment' in document 2018-05-23 01:51:23 +09:00
Naoki Takezoe
0f52dc4d8c Merge pull request #2019 from kazuki43zoo/disabled-checkbox-on-preview
Disabled a checkbox of tasklist on preview
2018-05-22 20:56:56 +09:00
Kazuki Shimizu
3c956ac03e Disabled a checkbox of tasklist on preview 2018-05-19 23:23:22 +09:00
KOUNOIKE Yuusuke
d45cba30c0 forgot add xml 2018-05-19 18:41:58 +09:00
KOUNOIKE Yuusuke
0840081dc8 use PBKDF2 for password. close #118 2018-05-19 18:19:49 +09:00
Kazuki Shimizu
0c0da0cbf7 Create comment on changing issue title 2018-05-19 17:48:30 +09:00
KOUNOIKE Yuusuke
e3641d0bf7 improve archive download 2018-05-19 15:02:48 +09:00
Kazuki Shimizu
1c118b8cd7 Change to avatar image link from avatar image 2018-05-19 14:32:40 +09:00
Naoki Takezoe
abf516682b Merge pull request #2011 from scf37/securerandom
Use SecureRandom to generate access tokens.
2018-05-18 14:16:24 +09:00
Scf37
72d07422a4 Use SecureRandom to generate access tokens.
scala.util.Random uses java.util.Random which only provides 64 bits of randomness.
2018-05-17 20:16:37 +03:00
Naoki Takezoe
ecc50cd2ae Merge pull request #2007 from kazuki43zoo/allow-task-list-on-commitcomment
Allow task list on commit comment area
2018-05-15 12:22:52 +09:00
Naoki Takezoe
acbcb60629 Merge pull request #1986 from kounoike/pr-ace-mode-by-js
set ace editor mode by ext-modelist.js
2018-05-12 23:03:36 +09:00
Naoki Takezoe
23a9bf46a2 Merge pull request #1989 from kounoike/pr-show-mail
Show mail address in profile page
2018-05-12 22:49:39 +09:00
Kazuki Shimizu
342ad68212 Allow task list on commit comment area 2018-05-12 21:26:19 +09:00
Naoki Takezoe
6d3dec518f Merge pull request #2006 from kazuki43zoo/gh-1976_download-patch-file-on-PR
Download a correct patch file on PR screen
2018-05-12 18:07:07 +09:00
Naoki Takezoe
e350633b69 (refs #2004) Fix review comments duplication 2018-05-12 18:06:26 +09:00
Kazuki Shimizu
4e3be1deb5 Download a correct patch file on PR screen
Fixes gh-1976
2018-05-12 17:49:29 +09:00
Naoki Takezoe
dc290614ca Merge pull request #2003 from kazuki43zoo/view-badge-on-pulls
View badges on pull request screen
2018-05-12 17:24:46 +09:00
Naoki Takezoe
1b22c2e29b Fix invisible tag location to fix button group style 2018-05-12 16:53:59 +09:00
Kazuki Shimizu
eedbd9f45a View badges on pull request screen 2018-05-12 12:08:59 +09:00
Naoki Takezoe
fa29acef54 (refs #2002) Recover commit comment for the specific commits 2018-05-11 16:09:44 +09:00
Naoki Takezoe
6355f8d0a4 Merge pull request #2001 from xuwei-k/sbt-1.1.5
sbt 1.1.5
2018-05-10 13:56:32 +09:00
Naoki Takezoe
173fc30211 (refs #2000) Recover title editing of pull request 2018-05-09 17:06:45 +09:00
kenji yoshida
4df9c36d82 sbt 1.1.5 2018-05-09 15:47:19 +09:00
Naoki Takezoe
33c0fb680e Merge pull request #1996 from kounoike/fix-json-diff
Fix JSON diff issue.
2018-05-06 02:26:06 +09:00
KOUNOIKE Yuusuke
378b3986dc Fix JSON diff issue. 2018-05-04 14:39:29 +09:00
Naoki Takezoe
f321d0974e Update README.md 2018-05-03 11:05:25 +09:00
Naoki Takezoe
227e2786e1 Merge pull request #1994 from kounoike/pr-show-email-settings-error
Show SMTP Error message in testing email settings
2018-05-02 16:50:56 +09:00
KOUNOIKE Yuusuke
1a90fd86ff format 2018-05-02 14:07:19 +09:00
KOUNOIKE Yuusuke
49f095bb26 Show SMTP Error message in testing email settings 2018-05-02 13:55:16 +09:00
Naoki Takezoe
b9acfc62c6 Use random UUID as blowfish key 2018-05-01 08:39:30 +09:00
Naoki Takezoe
864df6cdac Use random UUID as blowfish key 2018-05-01 08:27:54 +09:00
Naoki Takezoe
f0f4b8faa6 Bump to GitBucket 4.24.1 2018-05-01 01:41:52 +09:00
Naoki Takezoe
6350354942 Merge pull request #1991 from kounoike/fix-branch-protect
Fix branch protection problem
2018-05-01 01:39:16 +09:00
KOUNOIKE Yuusuke
bde66b2896 don't use :repo, should use :repository 2018-05-01 01:23:09 +09:00
Naoki Takezoe
173669f75e Fix cancel button style 2018-04-30 03:29:26 +09:00
KOUNOIKE Yuusuke
f53497da56 forgot radio group change. 2018-04-29 20:51:34 +09:00
KOUNOIKE Yuusuke
70dbee839a Show mail address in profile page, It can be controlled by settings. closes #673. 2018-04-29 19:05:45 +09:00
KOUNOIKE Yuusuke
59859359ea set ace editor mode by ext-modelist.js 2018-04-29 16:37:51 +09:00
54 changed files with 689 additions and 493 deletions

View File

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

View File

@@ -1,4 +1,4 @@
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.12)
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.svg)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.12) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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.

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
sbt.version=1.1.3
sbt.version=1.1.5

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>")
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -57,6 +57,7 @@ $(function(){
enableTaskList : @enableTaskList
}, function(data){
$('#preview-area@uid').html(data);
$('#preview-area@uid input').prop('disabled', true);
prettyPrint();
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)&nbsp;@helpers.user(commit.authorName, commit.authorEmailAddress)</td>
<td style="width: 200px;">@helpers.avatarLink(commit, 20)&nbsp;@helpers.user(commit.authorName, commit.authorEmailAddress)</td>
<td>
<span class="muted">@gitbucket.core.helper.html.datetimeago(commit.authorTime):</span>&nbsp;@commit.shortMessage
</td>
@@ -70,4 +70,4 @@
});
</script>
}
}
}

View File

@@ -138,10 +138,6 @@ div.content-wrapper {
/* ======================================================================== */
/* Global Header */
/* ======================================================================== */
span.header-version {
font-size: small;
}
.main-header .logo img {
width: 24px;
height: 24px;

View File

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

View File

@@ -134,7 +134,8 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
ldap = None,
oidcAuthentication = false,
oidc = None,
skinName = "skin-blue"
skinName = "skin-blue",
showMailAddress = false
)
/**