Compare commits

..

74 Commits
quill ... 3.14

Author SHA1 Message Date
Naoki Takezoe
a79f105eea Ready for 3.14 release 2016-04-30 15:08:31 +09:00
Naoki Takezoe
b916595da3 (refs #1154)Fix path of request to the branch protection API 2016-04-20 01:13:08 +09:00
Naoki Takezoe
90b63090cc Merge branch 'McFoggy-webhook-content-type' 2016-04-16 00:25:15 +09:00
Naoki Takezoe
345685ed7c Add Version 3.14 2016-04-16 00:24:52 +09:00
Naoki Takezoe
1d9fe5770e Merge branch 'webhook-content-type' of https://github.com/McFoggy/gitbucket into McFoggy-webhook-content-type
# Conflicts:
#	src/main/scala/gitbucket/core/service/WebHookService.scala
#	src/main/twirl/gitbucket/core/settings/edithooks.scala.html
2016-04-16 00:22:25 +09:00
Naoki Takezoe
ec307b84d3 Merge pull request #1169 from McFoggy/issue-1168
correct empty security token usage, fixes #1168
2016-04-14 14:03:36 +09:00
Matthieu Brouillard
5d7346db91 correct empty security token usage, fixes #1168 2016-04-11 13:15:02 +02:00
Naoki Takezoe
443498433d Fix CSS style 2016-04-09 22:37:18 +09:00
Naoki Takezoe
a58ce07736 (refs #1166)BugFix: Show more pages link of Wiki does not work 2016-04-09 14:45:25 +09:00
Naoki Takezoe
1903c3990c (refs #1160)Fix mobile view 2016-04-08 14:06:35 +09:00
Naoki Takezoe
90441c8eec (refs #1161)Add new extension point to add dashboard tab 2016-04-04 23:57:41 +09:00
Naoki Takezoe
601919bcc6 (refs #1161)Add new extension point to add system setting menu and account setting menu 2016-04-04 23:50:49 +09:00
Naoki Takezoe
6903b096f5 (refs #1161)Add new extension point to add repository setting tab 2016-04-04 23:38:01 +09:00
Naoki Takezoe
e44fed09fa (refs #1161)Add new extension point to add repository menu 2016-04-04 23:31:05 +09:00
Naoki Takezoe
8ed0c8a170 Fix width of line number column in diff view 2016-04-03 16:57:39 +09:00
Naoki Takezoe
31118ac285 Tweak some styles in Wiki 2016-04-03 15:55:30 +09:00
Naoki Takezoe
c851b7582f (refs #1161)Add new extension point to add a tab to the profile page 2016-04-03 03:02:06 +09:00
Naoki Takezoe
102a02d527 (refs #1161)Add new extension point to add global menu 2016-04-03 00:50:57 +09:00
Naoki Takezoe
1968bf871a Update version to 3.14.0-SNAPSHOT 2016-04-03 00:44:19 +09:00
Naoki Takezoe
5cd4e48173 Merge pull request #1105 from ritschwumm/wip/plugin
make Plugin an abstract class to improve binary compatibility
2016-04-03 00:43:13 +09:00
Naoki Takezoe
111d212cb5 Merged branch wiki_attach_image into master 2016-04-02 23:26:35 +09:00
Naoki Takezoe
d648d34393 Add security checking for file attachment in Wiki 2016-04-02 23:23:51 +09:00
Naoki Takezoe
bbaa5b38e7 Bump wagon-ssh 2016-04-02 01:55:49 +09:00
Naoki Takezoe
3c50a78be2 Attach image to wiki is progressing 2016-04-01 21:49:25 +09:00
Naoki Takezoe
82cc1fa530 Merged branch master into master 2016-04-01 20:19:06 +09:00
Naoki Takezoe
8c0581973e (refs #79)Wiki page search is available 2016-04-01 20:18:58 +09:00
Naoki Takezoe
bfa15f5d75 Merge pull request #1158 from ledyba/fix_issues_layout
Fix layouts for Issue pages.
2016-04-01 16:29:49 +09:00
PSI
57e87b581d use row-fluid class instead of row for full-width layout. 2016-04-01 14:31:45 +09:00
Naoki Takezoe
5aa6f5bce3 Update GitBucketVersion to 3.13 2016-04-01 02:00:11 +09:00
Naoki Takezoe
9ba098a805 Update README.md 2016-04-01 01:58:46 +09:00
Naoki Takezoe
b2d8567c26 (refs #1152)Fix label and milestone editing 2016-03-31 19:19:06 +09:00
Naoki Takezoe
19d97c93ce Fix layout of wiki editing form and history 2016-03-31 18:10:16 +09:00
Naoki Takezoe
cad2daa2f9 Merge pull request #1150 from distkloc/commit-message-from-updating-branch
Replace embedded variables in commit message Update Branch button generates
2016-03-30 01:26:52 +09:00
distkloc
585f0b5769 Replace embedded variables in commit message Update Branch button generates with their values 2016-03-29 00:47:09 +09:00
Naoki Takezoe
dd23d1109b BugFix for "Show more repositories" link 2016-03-28 08:35:32 +09:00
Naoki Takezoe
5e84221d39 Merge pull request #1156 from gitbucket/update_ui_sidebar
Move the repository menu to the sidebar
2016-03-28 00:50:53 +09:00
Naoki Takezoe
faf3e6c26b Fix dashboard and search layout 2016-03-28 00:34:48 +09:00
Naoki Takezoe
969da2c63b Tweak CSS styles 2016-03-27 22:53:54 +09:00
Naoki Takezoe
ba61891510 Move the repository menu to the sidebar 2016-03-27 19:05:09 +09:00
Naoki Takezoe
a581871a89 Fix Branches, Tags and Milestones presentaion 2016-03-24 20:17:27 +09:00
Naoki Takezoe
d96e1fa503 Move Branches and Tags link to repository navigation tab 2016-03-24 20:08:55 +09:00
Naoki Takezoe
b66812d76c Fix editor styles 2016-03-24 16:53:22 +09:00
Naoki Takezoe
ae32016856 Update dropdown UI 2016-03-24 15:25:44 +09:00
Naoki Takezoe
56aec15e68 Simplify dashboard UI 2016-03-24 14:48:41 +09:00
Naoki Takezoe
d0c8e33ec5 Separate labels and milestones from issues 2016-03-22 15:19:08 +09:00
Naoki Takezoe
7ab260e688 Simplify issue and pull request creation form 2016-03-22 14:22:20 +09:00
Naoki Takezoe
0d2c923664 Fix broken commits link in the repository header 2016-03-22 08:14:31 +09:00
Naoki Takezoe
e7a47fe3a4 Update file browser and commit list UI 2016-03-22 00:33:07 +09:00
Naoki Takezoe
e454f78c5a Merge pull request #1153 from gitbucket/bootstrap3-default-theme
Move to raw Bootstrap3 from GitHub like theme for Bootstrap3
2016-03-20 21:05:26 +09:00
Naoki Takezoe
192e4ade3e Adjust top margin of comment preview tab 2016-03-19 12:27:23 +09:00
Naoki Takezoe
733797cb6f Remove tab icons 2016-03-18 02:07:04 +09:00
Naoki Takezoe
f0e4157a46 Fix issues and pull request title editing form 2016-03-18 00:50:12 +09:00
Naoki Takezoe
2ad6948bb4 Fix styles of diff view 2016-03-17 01:50:24 +09:00
Naoki Takezoe
dd46d649a6 Fix styles of account setting pages 2016-03-17 01:38:32 +09:00
Naoki Takezoe
bbe8a9b9e4 Adjust sidebar of issues 2016-03-16 16:15:31 +09:00
Naoki Takezoe
376b109602 Simplify commit list 2016-03-16 16:07:33 +09:00
Naoki Takezoe
1d085d52bb Small fix for UI styles 2016-03-16 10:36:52 +09:00
Naoki Takezoe
d1f42e0ed7 Move repository status to right of the header 2016-03-16 09:52:49 +09:00
Naoki Takezoe
101f8598ed Change Fork button to tab 2016-03-16 02:37:16 +09:00
Naoki Takezoe
da62f6f8fb Simplify file list table 2016-03-16 02:12:23 +09:00
Naoki Takezoe
5750286b5d Remove unused style definitions 2016-03-15 22:44:06 +09:00
Naoki Takezoe
f2ca4fb64b Adjust styles 2016-03-15 22:31:44 +09:00
Naoki Takezoe
5f6b577cbf Adjust styles 2016-03-15 21:09:00 +09:00
Naoki Takezoe
62004c279c Move to raw Bootstrap3 from GitHub like theme for Bootstrap3 2016-03-15 02:35:48 +09:00
Naoki Takezoe
838c7fb991 Merge pull request #1149 from McFoggy/github-complaint-readme
rephrase README.md in regard of github complaint
2016-03-12 23:59:02 +09:00
Matthieu Brouillard
93786f0fd6 rephrase README.md in regard of github complaint 2016-03-12 14:43:51 +01:00
Naoki Takezoe
f3514e5625 (refs #1146)BugFix for choosing user type in grpup 2016-03-12 04:11:30 +09:00
Naoki Takezoe
a2b0ee0c24 Merge pull request #1145 from xuwei-k/GenBCode
update Scala 2.11.8, use new Java8 Backend
2016-03-10 14:31:11 +09:00
xuwei-k
2bcab30529 update Scala 2.11.8, use new Java8 Backend
- https://github.com/scala/make-release-notes/blob/9cfbdc8c92f94/experimental-backend.md#emitting-java-8-style-lambdas
- http://d.hatena.ne.jp/xuwei/20150626/1435282696
2016-03-10 14:07:48 +09:00
nazoking
91cda6d245 Merge pull request #1144 from distkloc/pull-request-key-in-issues-api
Add pull_request key in list issues api if an issue is a pull request
2016-03-10 12:35:48 +09:00
distkloc
a82e579d57 Add pull_request key in list issues api if an issue is a pull request 2016-03-10 02:08:09 +09:00
Naoki Takezoe
94421c7a63 Fix font size of Wiki sidebar and footer 2016-03-10 02:07:46 +09:00
Matthieu Brouillard
6431d25409 add possibility to choose content type for repository webhooks 2016-03-06 09:51:33 +01:00
Herr Ritschwumm
ff3205b6c7 make Plugin an abstract class to improve binary compatibility 2016-02-13 02:53:28 +01:00
140 changed files with 6945 additions and 13150 deletions

View File

@@ -1,7 +1,10 @@
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.png)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket) GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.png)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket)
========= =========
GitBucket is a GitHub clone powered by Scala which has easy installation and high extensibility. GitBucket is a Git platform powered by Scala offering:
- easy installation
- high extensibility by plugins
- API compatibility with Github
Features Features
-------- --------
@@ -56,10 +59,28 @@ Support
- Make sure check whether there is a same question or request in the past. - Make sure check whether there is a same question or request in the past.
- When raise a new issue, write subject in **English** at least. - When raise a new issue, write subject in **English** at least.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja). - We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it. - First priority of GitBucket is easy installation and API compatibility with GitHub, so we might reject if your request is against it.
Release Notes Release Notes
-------- --------
### 4.0 - 30 Apr 2016
- MySQL and PostgreSQL support
- Data export and import
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
### 3.14 - 30 Apr 2016
- File attachment and search for wiki pages
- New extension points to add menus
- Content-Type of webhooks has been choosable
### 3.13 - 1 Apr 2016
- Refresh user interface for wide screen
- Add `pull_request` key in list issues API for pull requests
- Add `X-Hub-Signature` security to webhooks
- Provide SHA-256 checksum for `gitbucket.war`
### 3.12 - 27 Feb 2016 ### 3.12 - 27 Feb 2016
- New GitHub UI - New GitHub UI
- Improve mobile view - Improve mobile view

View File

@@ -1,6 +1,6 @@
val Organization = "gitbucket" val Organization = "gitbucket"
val Name = "gitbucket" val Name = "gitbucket"
val GitBucketVersion = "3.12.0" val GitBucketVersion = "3.14.0"
val ScalatraVersion = "2.4.0" val ScalatraVersion = "2.4.0"
val JettyVersion = "9.3.6.v20151106" val JettyVersion = "9.3.6.v20151106"
@@ -10,7 +10,7 @@ sourcesInBase := false
organization := Organization organization := Organization
name := Name name := Name
version := GitBucketVersion version := GitBucketVersion
scalaVersion := "2.11.7" scalaVersion := "2.11.8"
// dependency settings // dependency settings
resolvers ++= Seq( resolvers ++= Seq(
@@ -18,6 +18,7 @@ resolvers ++= Seq(
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/" "sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
) )
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0",
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r", "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r", "org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r",
"org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra" % ScalatraVersion,
@@ -25,7 +26,7 @@ libraryDependencies ++= Seq(
"org.json4s" %% "json4s-jackson" % "3.3.0", "org.json4s" %% "json4s-jackson" % "3.3.0",
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0", "io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
"commons-io" % "commons-io" % "2.4", "commons-io" % "commons-io" % "2.4",
"io.github.gitbucket" % "markedj" % "1.0.7", "io.github.gitbucket" % "markedj" % "1.0.8",
"org.apache.commons" % "commons-compress" % "1.10", "org.apache.commons" % "commons-compress" % "1.10",
"org.apache.commons" % "commons-email" % "1.4", "org.apache.commons" % "commons-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.1", "org.apache.httpcomponents" % "httpclient" % "4.5.1",
@@ -38,7 +39,6 @@ libraryDependencies ++= Seq(
"com.mchange" % "c3p0" % "0.9.5.2", "com.mchange" % "c3p0" % "0.9.5.2",
"com.typesafe" % "config" % "1.3.0", "com.typesafe" % "config" % "1.3.0",
"com.typesafe.akka" %% "akka-actor" % "2.3.14", "com.typesafe.akka" %% "akka-actor" % "2.3.14",
"io.getquill" %% "quill-jdbc" % "0.4.1",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"), "com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
@@ -52,7 +52,7 @@ libraryDependencies ++= Seq(
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._" play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
// Compiler settings // Compiler settings
scalacOptions := Seq("-deprecation", "-language:postfixOps") scalacOptions := Seq("-deprecation", "-language:postfixOps", "-Ybackend:GenBCode", "-Ydelambdafy:method", "-target:jvm-1.8")
javacOptions in compile ++= Seq("-target", "8", "-source", "8") javacOptions in compile ++= Seq("-target", "8", "-source", "8")
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml" javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console") testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console")

View File

@@ -10,7 +10,7 @@
<extension> <extension>
<groupId>org.apache.maven.wagon</groupId> <groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId> <artifactId>wagon-ssh</artifactId>
<version>1.0-beta-6</version> <version>2.10</version>
</extension> </extension>
</extensions> </extensions>
</build> </build>

View File

@@ -1,8 +0,0 @@
db.dataSourceClassName="org.h2.jdbcx.JdbcDataSource"
db.dataSource.url="jdbc:h2:~/.gitbucket/data;MVCC=true"
db.dataSource.user="sa"
db.dataSource.password="sa"
#db.dataSource.cachePrepStmts=true
#db.dataSource.prepStmtCacheSize=250
#db.dataSource.prepStmtCacheSqlLimit=2048
#db.connectionTimeout=30000

View File

@@ -1 +1 @@
ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100); ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100);

View File

@@ -0,0 +1,3 @@
ALTER TABLE WEB_HOOK ADD COLUMN CTYPE VARCHAR(10);
UPDATE WEB_HOOK SET CTYPE = 'form';

View File

@@ -20,6 +20,16 @@ case class ApiIssue(
body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){ body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments") val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}") val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
val pull_request = if (isPullRequest) {
Some(Map(
"url" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${number}"),
"html_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}")
// "diff_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.diff"),
// "patch_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.patch")
))
} else {
None
}
} }
object ApiIssue{ object ApiIssue{

View File

@@ -39,7 +39,7 @@ object ApiRepository{
description = repository.description.getOrElse(""), description = repository.description.getOrElse(""),
watchers = 0, watchers = 0,
forks = forkedCount, forks = forkedCount,
`private` = repository.`private`, `private` = repository.isPrivate,
default_branch = repository.defaultBranch, default_branch = repository.defaultBranch,
owner = owner owner = owner
)(urlIsHtmlUrl) )(urlIsHtmlUrl)

View File

@@ -29,8 +29,8 @@ object ApiUser{
def apply(user: Account): ApiUser = ApiUser( def apply(user: Account): ApiUser = ApiUser(
login = user.userName, login = user.userName,
email = user.mailAddress, email = user.mailAddress,
`type` = if(user.groupAccount){ "Organization" }else{ "User" }, `type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
site_admin = user.administrator, site_admin = user.isAdmin,
created_at = user.registeredDate created_at = user.registeredDate
) )
} }

View File

@@ -114,23 +114,23 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// Public Activity // Public Activity
case "activity" => case "activity" =>
gitbucket.core.account.html.activity(account, gitbucket.core.account.html.activity(account,
if(account.groupAccount) Nil else getGroupsByUserName(userName), if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getActivitiesByUser(userName, true)) getActivitiesByUser(userName, true))
// Members // Members
case "members" if(account.groupAccount) => { case "members" if(account.isGroupAccount) => {
val members = getGroupMembers(account.userName) val members = getGroupMembers(account.userName)
gitbucket.core.account.html.members(account, members.map(_.userName), gitbucket.core.account.html.members(account, members.map(_.userName),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.manager })) context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
} }
// Repositories // Repositories
case _ => { case _ => {
val members = getGroupMembers(account.userName) val members = getGroupMembers(account.userName)
gitbucket.core.account.html.repositories(account, gitbucket.core.account.html.repositories(account,
if(account.groupAccount) Nil else getGroupsByUserName(userName), if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getVisibleRepositories(context.loginAccount, Some(userName)), getVisibleRepositories(context.loginAccount, Some(userName)),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.manager })) context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
} }
} }
} getOrElse NotFound } getOrElse NotFound
@@ -190,7 +190,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// removeUserRelatedData(userName) // removeUserRelatedData(userName)
removeUserRelatedData(userName) removeUserRelatedData(userName)
updateAccount(account.copy(removed = true)) updateAccount(account.copy(isRemoved = true))
} }
session.invalidate session.invalidate
@@ -360,7 +360,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
case _: List[String] => case _: List[String] =>
val managerPermissions = groups.map { group => val managerPermissions = groups.map { group =>
val members = getGroupMembers(group) val members = getGroupMembers(group)
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.manager }) context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
} }
helper.html.forkrepository( helper.html.forkrepository(
repository, repository,
@@ -389,7 +389,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
repositoryName = repository.name, repositoryName = repository.name,
userName = accountName, userName = accountName,
description = repository.repository.description, description = repository.repository.description,
isPrivate = repository.repository.`private`, isPrivate = repository.repository.isPrivate,
originRepositoryName = Some(originRepositoryName), originRepositoryName = Some(originRepositoryName),
originUserName = Some(originUserName), originUserName = Some(originUserName),
parentRepositoryName = Some(repository.name), parentRepositoryName = Some(repository.name),
@@ -398,7 +398,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// Add collaborators for group repository // Add collaborators for group repository
val ownerAccount = getAccountByUserName(accountName).get val ownerAccount = getAccountByUserName(accountName).get
if(ownerAccount.groupAccount){ if(ownerAccount.isGroupAccount){
getGroupMembers(accountName).foreach { member => getGroupMembers(accountName).foreach { member =>
addCollaborator(accountName, repository.name, member.userName) addCollaborator(accountName, repository.name, member.userName)
} }

View File

@@ -50,7 +50,7 @@ abstract class ControllerBase extends ScalatraFilter
if(account == null){ if(account == null){
// Redirect to login form // Redirect to login form
httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path)) httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path))
} else if(account.administrator){ } else if(account.isAdmin){
// H2 Console (administrators only) // H2 Console (administrators only)
chain.doFilter(request, response) chain.doFilter(request, response)
} else { } else {

View File

@@ -108,7 +108,9 @@ trait DashboardControllerBase extends ControllerBase {
case _ => condition.copy(author = Some(userName)) case _ => condition.copy(author = Some(userName))
}, },
filter, filter,
getGroupNames(userName)) getGroupNames(userName),
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true),
getUserRepositories(userName, withoutPhysicalInfo = true))
} }
private def searchPullRequests(filter: String) = { private def searchPullRequests(filter: String) = {
@@ -131,7 +133,9 @@ trait DashboardControllerBase extends ControllerBase {
case _ => condition.copy(author = Some(userName)) case _ => condition.copy(author = Some(userName))
}, },
filter, filter,
getGroupNames(userName)) getGroupNames(userName),
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true),
getUserRepositories(userName, withoutPhysicalInfo = true))
} }

View File

@@ -1,18 +1,24 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.util.{Keys, FileUtil} import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.servlet.Database
import gitbucket.core.util._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
import org.scalatra._ import org.scalatra._
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem} import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
import org.apache.commons.io.FileUtils import org.apache.commons.io.{IOUtils, FileUtils}
/** /**
* Provides Ajax based file upload functionality. * Provides Ajax based file upload functionality.
* *
* This servlet saves uploaded file. * This servlet saves uploaded file.
*/ */
class FileUploadController extends ScalatraServlet with FileUploadSupport { class FileUploadController extends ScalatraServlet with FileUploadSupport with RepositoryService with AccountService {
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024))) configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
@@ -31,6 +37,54 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
}, FileUtil.isUploadableType) }, FileUtil.isUploadableType)
} }
post("/wiki/:owner/:repository"){
// Don't accept not logged-in users
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account =>
val owner = params("owner")
val repository = params("repository")
// Check whether logged-in user is collaborator
collaboratorsOnly(owner, repository, loginAccount){
execute({ (file, fileId) =>
val fileName = file.getName
LockUtil.lock(s"${owner}/${repository}/wiki") {
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
if(headId != null){
JGitUtil.processTree(git, headId){ (path, tree) =>
if(path != fileName){
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
}
}
val bytes = IOUtils.toByteArray(file.getInputStream)
builder.add(JGitUtil.createDirCacheEntry(fileName, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes)))
builder.finish()
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
Constants.HEAD, loginAccount.userName, loginAccount.mailAddress, s"Uploaded ${fileName}")
fileName
}
}
}, FileUtil.isImage)
}
} getOrElse BadRequest
}
private def collaboratorsOnly(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
implicit val session = Database.getSession(request)
loginAccount match {
case x if(x.isAdmin) => action
case x if(getCollaborators(owner, repository).contains(x.userName)) => action
case _ => BadRequest
}
}
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match { private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
case Some(file) if(mimeTypeChcker(file.name)) => case Some(file) if(mimeTypeChcker(file.name)) =>
defining(FileUtil.generateFileId){ fileId => defining(FileUtil.generateFileId){ fileId =>

View File

@@ -1,9 +1,8 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.helper.xml import gitbucket.core.helper.xml
import gitbucket.core.model.Account import gitbucket.core.model.Account
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService, RepositorySearchService, IssuesService} import gitbucket.core.service._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil} import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
@@ -110,7 +109,7 @@ trait IndexControllerBase extends ControllerBase {
get("/_user/proposals")(usersOnly { get("/_user/proposals")(usersOnly {
contentType = formats("json") contentType = formats("json")
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map("options" -> getAllUsers(false).filter(!_.groupAccount).map(_.userName).toArray) Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray)
) )
}) })
@@ -138,13 +137,21 @@ trait IndexControllerBase extends ControllerBase {
target.toLowerCase match { target.toLowerCase match {
case "issue" => gitbucket.core.search.html.issues( case "issue" => gitbucket.core.search.html.issues(
searchIssues(repository.owner, repository.name, query),
countFiles(repository.owner, repository.name, query), countFiles(repository.owner, repository.name, query),
searchIssues(repository.owner, repository.name, query),
countWikiPages(repository.owner, repository.name, query),
query, page, repository)
case "wiki" => gitbucket.core.search.html.wiki(
countFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query),
searchWikiPages(repository.owner, repository.name, query),
query, page, repository) query, page, repository)
case _ => gitbucket.core.search.html.code( case _ => gitbucket.core.search.html.code(
searchFiles(repository.owner, repository.name, query), searchFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query), countIssues(repository.owner, repository.name, query),
countWikiPages(repository.owner, repository.name, query),
query, page, repository) query, page, repository)
} }
} }

View File

@@ -67,7 +67,7 @@ trait IssuesControllerBase extends ControllerBase {
_, _,
getComments(owner, name, issueId.toInt), getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt), getIssueLabels(owner, name, issueId.toInt),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.groupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name), getMilestonesWithIssueCount(owner, name),
getLabels(owner, name), getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
@@ -79,7 +79,7 @@ trait IssuesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/new")(readableUsersOnly { repository => get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
html.create( html.create(
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.groupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestones(owner, name), getMilestones(owner, name),
getLabels(owner, name), getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
@@ -380,7 +380,7 @@ trait IssuesControllerBase extends ControllerBase {
"issues", "issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page, page,
if(!getAccountByUserName(owner).exists(_.groupAccount)){ if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted (getCollaborators(owner, repoName) :+ owner).sorted
} else { } else {
getCollaborators(owner, repoName) getCollaborators(owner, repoName)

View File

@@ -23,6 +23,7 @@ trait LabelsControllerBase extends ControllerBase {
"labelColor" -> trim(label("Color", text(required, color))) "labelColor" -> trim(label("Color", text(required, color)))
)(LabelForm.apply) )(LabelForm.apply)
get("/:owner/:repository/issues/labels")(referrersOnly { repository => get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
html.list( html.list(
getLabels(repository.owner, repository.name), getLabels(repository.owner, repository.name),
@@ -84,7 +85,11 @@ trait LabelsControllerBase extends ControllerBase {
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = { override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
val owner = params("owner") val owner = params("owner")
val repository = params("repository") val repository = params("repository")
getLabel(owner, repository, value).map(_ => "Name has already been taken.") params.get("labelId").map { labelId =>
getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.")
}.getOrElse {
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
}
} }
} }

View File

@@ -94,7 +94,7 @@ trait PullRequestsControllerBase extends ControllerBase {
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId)) (commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
.sortWith((a, b) => a.registeredDate before b.registeredDate), .sortWith((a, b) => a.registeredDate before b.registeredDate),
getIssueLabels(owner, name, issueId), getIssueLabels(owner, name, issueId),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.groupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name), getMilestonesWithIssueCount(owner, name),
getLabels(owner, name), getLabels(owner, name),
commits, commits,
@@ -178,7 +178,7 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet
pullRemote(owner, name, pullreq.requestBranch, pullreq.userName, pullreq.repositoryName, pullreq.branch, loginAccount, pullRemote(owner, name, pullreq.requestBranch, pullreq.userName, pullreq.repositoryName, pullreq.branch, loginAccount,
"Merge branch '${alias}' into ${pullreq.requestBranch}") match { s"Merge branch '${alias}' into ${pullreq.requestBranch}") match {
case None => // conflict case None => // conflict
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}." flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
case Some(oldId) => case Some(oldId) =>
@@ -370,7 +370,7 @@ trait PullRequestsControllerBase extends ControllerBase {
originRepository, originRepository,
forkedRepository, forkedRepository,
hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount), hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount),
(getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.groupAccount) Nil else List(originRepository.owner))).sorted, (getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.isGroupAccount) Nil else List(originRepository.owner))).sorted,
getMilestones(originRepository.owner, originRepository.name), getMilestones(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name) getLabels(originRepository.owner, originRepository.name)
) )
@@ -524,7 +524,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"pulls", "pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
page, page,
if(!getAccountByUserName(owner).exists(_.groupAccount)){ if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted (getCollaborators(owner, repoName) :+ owner).sorted
} else { } else {
getCollaborators(owner, repoName) getCollaborators(owner, repoName)

View File

@@ -15,6 +15,7 @@ import org.scalatra.i18n.Messages
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
import gitbucket.core.model.WebHookContentType
class RepositorySettingsController extends RepositorySettingsControllerBase class RepositorySettingsController extends RepositorySettingsControllerBase
@@ -49,13 +50,16 @@ trait RepositorySettingsControllerBase extends ControllerBase {
)(CollaboratorForm.apply) )(CollaboratorForm.apply)
// for web hook url addition // for web hook url addition
case class WebHookForm(url: String, events: Set[WebHook.Event], token: Option[String]) case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
def webHookForm(update:Boolean) = mapping( def webHookForm(update:Boolean) = mapping(
"url" -> trim(label("url", text(required, webHook(update)))), "url" -> trim(label("url", text(required, webHook(update)))),
"events" -> webhookEvents, "events" -> webhookEvents,
"ctype" -> label("ctype", text()),
"token" -> optional(trim(label("token", text(maxlength(100))))) "token" -> optional(trim(label("token", text(maxlength(100)))))
)(WebHookForm.apply) )(
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
)
// for transfer ownership // for transfer ownership
case class TransferOwnerShipForm(newOwner: String) case class TransferOwnerShipForm(newOwner: String)
@@ -87,7 +91,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
repository.name, repository.name,
form.description, form.description,
repository.repository.parentUserName.map { _ => repository.repository.parentUserName.map { _ =>
repository.repository.`private` repository.repository.isPrivate
} getOrElse form.isPrivate } getOrElse form.isPrivate
) )
// Change repository name // Change repository name
@@ -148,7 +152,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
html.collaborators( html.collaborators(
getCollaborators(repository.owner, repository.name), getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.groupAccount, getAccountByUserName(repository.owner).get.isGroupAccount,
repository) repository)
}) })
@@ -156,7 +160,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the collaborator. * Add the collaborator.
*/ */
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
if(!getAccountByUserName(repository.owner).get.groupAccount){ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
addCollaborator(repository.owner, repository.name, form.userName) addCollaborator(repository.owner, repository.name, form.userName)
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
@@ -166,7 +170,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the collaborator. * Add the collaborator.
*/ */
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
if(!getAccountByUserName(repository.owner).get.groupAccount){ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
removeCollaborator(repository.owner, repository.name, params("name")) removeCollaborator(repository.owner, repository.name, params("name"))
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
@@ -183,7 +187,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook edit page. * Display the web hook edit page.
*/ */
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository => get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
val webhook = WebHook(repository.owner, repository.name, "", None) val webhook = WebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None)
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true) html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
}) })
@@ -191,7 +195,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the web hook URL. * Add the web hook URL.
*/ */
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) => post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
addWebHook(repository.owner, repository.name, form.url, form.events, form.token) addWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
flash += "info" -> s"Webhook ${form.url} created" flash += "info" -> s"Webhook ${form.url} created"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
@@ -221,7 +225,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
val url = params("url") val url = params("url")
val token = Some(params("token")) val token = Some(params("token"))
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, token) val ctype = WebHookContentType.valueOf(params("ctype"))
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token)
val dummyPayload = { val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get val ownerAccount = getAccountByUserName(repository.owner).get
val commits = if(repository.commitCount == 0) List.empty else git.log val commits = if(repository.commitCount == 0) List.empty else git.log
@@ -280,7 +285,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Update web hook settings. * Update web hook settings.
*/ */
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) => post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
updateWebHook(repository.owner, repository.name, form.url, form.events, form.token) updateWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
flash += "info" -> s"webhook ${form.url} updated" flash += "info" -> s"webhook ${form.url} updated"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
@@ -364,7 +369,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match { getAccountByUserName(value) match {
case None => Some("User does not exist.") case None => Some("User does not exist.")
case Some(x) if(x.groupAccount) case Some(x) if(x.isGroupAccount)
=> Some("User does not exist.") => Some("User does not exist.")
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName)) case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
=> Some("User can access this repository already.") => Some("User can access this repository already.")

View File

@@ -497,6 +497,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
getForkedRepositories( getForkedRepositories(
repository.repository.originUserName.getOrElse(repository.owner), repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name)), repository.repository.originRepositoryName.getOrElse(repository.name)),
context.loginAccount match {
case None => List()
case account: Option[Account] => getGroupsByUserName(account.get.userName)
}, // groups of current user
repository) repository)
}) })
@@ -507,13 +511,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val ref = multiParams("splat").head val ref = multiParams("splat").head
JGitUtil.getTreeId(git, ref).map{ treeId => JGitUtil.getTreeId(git, ref).map{ treeId =>
html.find(ref, html.find(ref, treeId, repository)
treeId,
repository,
context.loginAccount match {
case None => List()
case account: Option[Account] => getGroupsByUserName(account.get.userName)
})
} getOrElse NotFound } getOrElse NotFound
} }
}) })
@@ -575,10 +573,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.files(revision, repository, html.files(revision, repository,
if(path == ".") Nil else path.split("/").toList, // current path if(path == ".") Nil else path.split("/").toList, // current path
context.loginAccount match {
case None => List()
case account: Option[Account] => getGroupsByUserName(account.get.userName)
}, // groups of current user
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount), files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch), getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),

View File

@@ -157,7 +157,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
get("/admin/users")(adminOnly { get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false) val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
val users = getAllUsers(includeRemoved) val users = getAllUsers(includeRemoved)
val members = users.collect { case account if(account.groupAccount) => val members = users.collect { case account if(account.isGroupAccount) =>
account.userName -> getGroupMembers(account.userName).map(_.userName) account.userName -> getGroupMembers(account.userName).map(_.userName)
}.toMap }.toMap
@@ -196,12 +196,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
} }
updateAccount(account.copy( updateAccount(account.copy(
password = form.password.map(sha1).getOrElse(account.password), password = form.password.map(sha1).getOrElse(account.password),
fullName = form.fullName, fullName = form.fullName,
mailAddress = form.mailAddress, mailAddress = form.mailAddress,
administrator = form.isAdmin, isAdmin = form.isAdmin,
url = form.url, url = form.url,
removed = form.isRemoved)) isRemoved = form.isRemoved))
updateImage(userName, form.fileId, form.clearImage) updateImage(userName, form.fileId, form.clearImage)
redirect("/admin/users") redirect("/admin/users")

View File

@@ -12,7 +12,8 @@ import org.eclipse.jgit.api.Git
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
class WikiController extends WikiControllerBase class WikiController extends WikiControllerBase
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator with WikiService with RepositoryService with AccountService with ActivityService
with CollaboratorsAuthenticator with ReferrerAuthenticator
trait WikiControllerBase extends ControllerBase { trait WikiControllerBase extends ControllerBase {
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator => self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>

View File

@@ -11,7 +11,7 @@ trait AccountComponent { self: Profile =>
val fullName = column[String]("FULL_NAME") val fullName = column[String]("FULL_NAME")
val mailAddress = column[String]("MAIL_ADDRESS") val mailAddress = column[String]("MAIL_ADDRESS")
val password = column[String]("PASSWORD") val password = column[String]("PASSWORD")
val administrator = column[Boolean]("ADMINISTRATOR") val isAdmin = column[Boolean]("ADMINISTRATOR")
val url = column[String]("URL") val url = column[String]("URL")
val registeredDate = column[java.util.Date]("REGISTERED_DATE") val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
@@ -19,7 +19,7 @@ trait AccountComponent { self: Profile =>
val image = column[String]("IMAGE") val image = column[String]("IMAGE")
val groupAccount = column[Boolean]("GROUP_ACCOUNT") val groupAccount = column[Boolean]("GROUP_ACCOUNT")
val removed = column[Boolean]("REMOVED") val removed = column[Boolean]("REMOVED")
def * = (userName, fullName, mailAddress, password, administrator, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply) def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
} }
} }
@@ -28,12 +28,12 @@ case class Account(
fullName: String, fullName: String,
mailAddress: String, mailAddress: String,
password: String, password: String,
administrator: Boolean, isAdmin: Boolean,
url: Option[String], url: Option[String],
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date, updatedDate: java.util.Date,
lastLoginDate: Option[java.util.Date], lastLoginDate: Option[java.util.Date],
image: Option[String], image: Option[String],
groupAccount: Boolean, isGroupAccount: Boolean,
removed: Boolean isRemoved: Boolean
) )

View File

@@ -8,13 +8,13 @@ trait GroupMemberComponent { self: Profile =>
class GroupMembers(tag: Tag) extends Table[GroupMember](tag, "GROUP_MEMBER") { class GroupMembers(tag: Tag) extends Table[GroupMember](tag, "GROUP_MEMBER") {
val groupName = column[String]("GROUP_NAME", O PrimaryKey) val groupName = column[String]("GROUP_NAME", O PrimaryKey)
val userName = column[String]("USER_NAME", O PrimaryKey) val userName = column[String]("USER_NAME", O PrimaryKey)
val manager = column[Boolean]("MANAGER") val isManager = column[Boolean]("MANAGER")
def * = (groupName, userName, manager) <> (GroupMember.tupled, GroupMember.unapply) def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply)
} }
} }
case class GroupMember( case class GroupMember(
groupName: String, groupName: String,
userName: String, userName: String,
manager: Boolean isManager: Boolean
) )

View File

@@ -26,7 +26,7 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
case class Repository( case class Repository(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
`private`: Boolean, isPrivate: Boolean,
description: Option[String], description: Option[String],
defaultBranch: String, defaultBranch: String,
registeredDate: java.util.Date, registeredDate: java.util.Date,

View File

@@ -3,21 +3,42 @@ package gitbucket.core.model
trait WebHookComponent extends TemplateComponent { self: Profile => trait WebHookComponent extends TemplateComponent { self: Profile =>
import profile.simple._ import profile.simple._
implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
lazy val WebHooks = TableQuery[WebHooks] lazy val WebHooks = TableQuery[WebHooks]
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate { class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
val url = column[String]("URL") val url = column[String]("URL")
val token = column[Option[String]]("TOKEN", O.Nullable) val token = column[Option[String]]("TOKEN", O.Nullable)
def * = (userName, repositoryName, url, token) <> ((WebHook.apply _).tupled, WebHook.unapply) val ctype = column[WebHookContentType]("CTYPE", O.NotNull)
def * = (userName, repositoryName, url, ctype, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
} }
} }
case class WebHookContentType(val code: String, val ctype: String)
object WebHookContentType {
object JSON extends WebHookContentType("json", "application/json")
object FORM extends WebHookContentType("form", "application/x-www-form-urlencoded")
val values: Vector[WebHookContentType] = Vector(JSON, FORM)
private val map: Map[String, WebHookContentType] = values.map(enum => enum.code -> enum).toMap
def apply(code: String): WebHookContentType = map(code)
def valueOf(code: String): WebHookContentType = map(code)
def valueOpt(code: String): Option[WebHookContentType] = map.get(code)
}
case class WebHook( case class WebHook(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
url: String, url: String,
ctype: WebHookContentType,
token: Option[String] token: Option[String]
) )

View File

@@ -1,16 +1,18 @@
package gitbucket.core.plugin package gitbucket.core.plugin
import javax.servlet.ServletContext import javax.servlet.ServletContext
import gitbucket.core.controller.ControllerBase import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.model.Account
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Version import gitbucket.core.util.Version
/** /**
* Trait for define plugin interface. * Trait for define plugin interface.
* To provide plugin, put Plugin class which mixed in this trait into the package root. * To provide a plugin, put a Plugin class which extends this class into the package root.
*/ */
trait Plugin { abstract class Plugin {
val pluginId: String val pluginId: String
val pluginName: String val pluginName: String
@@ -77,6 +79,76 @@ trait Plugin {
*/ */
def receiveHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[ReceiveHook] = Nil def receiveHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[ReceiveHook] = Nil
/**
* Override to add global menus.
*/
val globalMenus: Seq[(Context) => Option[Link]] = Nil
/**
* Override to add global menus.
*/
def globalMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
/**
* Override to add repository menus.
*/
val repositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
/**
* Override to add repository menus.
*/
def repositoryMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
/**
* Override to add repository setting tabs.
*/
val repositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
/**
* Override to add repository setting tabs.
*/
def repositorySettingTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
/**
* Override to add profile tabs.
*/
val profileTabs: Seq[(Account, Context) => Option[Link]] = Nil
/**
* Override to add profile tabs.
*/
def profileTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Account, Context) => Option[Link]] = Nil
/**
* Override to add system setting menus.
*/
val systemSettingMenus: Seq[(Context) => Option[Link]] = Nil
/**
* Override to add system setting menus.
*/
def systemSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
/**
* Override to add account setting menus.
*/
val accountSettingMenus: Seq[(Context) => Option[Link]] = Nil
/**
* Override to add account setting menus.
*/
def accountSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
/**
* Override to add dashboard tabs.
*/
val dashboardTabs: Seq[(Context) => Option[Link]] = Nil
/**
* Override to add dashboard tabs.
*/
def dashboardTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
/** /**
* This method is invoked in initialization of plugin system. * This method is invoked in initialization of plugin system.
* Register plugin functionality to PluginRegistry. * Register plugin functionality to PluginRegistry.
@@ -100,6 +172,27 @@ trait Plugin {
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook => (receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook =>
registry.addReceiveHook(receiveHook) registry.addReceiveHook(receiveHook)
} }
(globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu =>
registry.addGlobalMenu(globalMenu)
}
(repositoryMenus ++ repositoryMenus(registry, context, settings)).foreach { repositoryMenu =>
registry.addRepositoryMenu(repositoryMenu)
}
(repositorySettingTabs ++ repositorySettingTabs(registry, context, settings)).foreach { repositorySettingTab =>
registry.addRepositorySettingTab(repositorySettingTab)
}
(profileTabs ++ profileTabs(registry, context, settings)).foreach { profileTab =>
registry.addProfileTab(profileTab)
}
(systemSettingMenus ++ systemSettingMenus(registry, context, settings)).foreach { systemSettingMenu =>
registry.addSystemSettingMenu(systemSettingMenu)
}
(accountSettingMenus ++ accountSettingMenus(registry, context, settings)).foreach { accountSettingMenu =>
registry.addAccountSettingMenu(accountSettingMenu)
}
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
registry.addDashboardTab(dashboardTab)
}
} }
/** /**

View File

@@ -3,9 +3,9 @@ package gitbucket.core.plugin
import java.io.{File, FilenameFilter, InputStream} import java.io.{File, FilenameFilter, InputStream}
import java.net.URLClassLoader import java.net.URLClassLoader
import javax.servlet.ServletContext import javax.servlet.ServletContext
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.controller.{Context, ControllerBase} import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.model.Account
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
@@ -33,6 +33,14 @@ class PluginRegistry {
private val receiveHooks = new ListBuffer[ReceiveHook] private val receiveHooks = new ListBuffer[ReceiveHook]
receiveHooks += new ProtectedBranchReceiveHook() receiveHooks += new ProtectedBranchReceiveHook()
private val globalMenus = new ListBuffer[(Context) => Option[Link]]
private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
private val profileTabs = new ListBuffer[(Account, Context) => Option[Link]]
private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]]
private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]]
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]]
def addPlugin(pluginInfo: PluginInfo): Unit = { def addPlugin(pluginInfo: PluginInfo): Unit = {
plugins += pluginInfo plugins += pluginInfo
} }
@@ -107,17 +115,47 @@ class PluginRegistry {
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
private case class GlobalAction( def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = {
method: String, globalMenus += globalMenu
path: String, }
function: (HttpServletRequest, HttpServletResponse, Context) => Any
)
private case class RepositoryAction( def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
method: String,
path: String, def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = {
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any repositoryMenus += repositoryMenu
) }
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = {
repositorySettingTabs += repositorySettingTab
}
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = {
profileTabs += profileTab
}
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = {
systemSettingMenus += systemSettingMenu
}
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = {
accountSettingMenus += accountSettingMenu
}
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = {
dashboardTabs += dashboardTab
}
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq
} }
@@ -201,6 +239,8 @@ object PluginRegistry {
} }
case class Link(id: String, label: String, path: String, icon: Option[String] = None)
case class PluginInfo( case class PluginInfo(
pluginId: String, pluginId: String,
pluginName: String, pluginName: String,

View File

@@ -5,8 +5,6 @@ import profile.simple._
import gitbucket.core.model.{Account, AccessToken} import gitbucket.core.model.{Account, AccessToken}
import gitbucket.core.util.StringUtil import gitbucket.core.util.StringUtil
import gitbucket.core.servlet.Database._
import io.getquill._
import scala.util.Random import scala.util.Random
@@ -29,36 +27,28 @@ trait AccessTokenService {
var hash: String = null var hash: String = null
do{ do{
token = makeAccessTokenString token = makeAccessTokenString
hash = tokenToHash(token) hash = tokenToHash(token)
} while ( }while(AccessTokens.filter(_.tokenHash === hash.bind).exists.run)
db.run(quote { (hash: String) => query[AccessToken].filter(_.tokenHash == hash).nonEmpty })(hash).head
)
val newToken = AccessToken( val newToken = AccessToken(
userName = userName, userName = userName,
note = note, note = note,
tokenHash = hash) tokenHash = hash)
// TODO Remain Slick code
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) += newToken val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) += newToken
(tokenId, token) (tokenId, token)
} }
def getAccountByAccessToken(token: String): Option[Account] = def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] =
db.run(quote { (tokenHash: String) => Accounts
query[AccessToken].filter(_.tokenHash == tokenHash) .innerJoin(AccessTokens)
.join(query[Account]).on { (t, a) => t.userName == a.userName && a.registeredDate == false } .filter{ case (ac, t) => (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) }
.map { case (t, a) => a } .map{ case (ac, t) => ac }
})(tokenToHash(token)).headOption .firstOption
def getAccessTokens(userName: String): List[AccessToken] = def getAccessTokens(userName: String)(implicit s: Session): List[AccessToken] =
db.run(quote { (userName: String) => AccessTokens.filter(_.userName === userName.bind).sortBy(_.accessTokenId.desc).list
query[AccessToken].filter(_.userName == userName).sortBy(_.accessTokenId)(Ord.desc)
})(userName)
def deleteAccessToken(userName: String, accessTokenId: Int): Unit = def deleteAccessToken(userName: String, accessTokenId: Int)(implicit s: Session): Unit =
db.run(quote { (userName: String, accessTokenId: Int) => AccessTokens filter (t => t.userName === userName.bind && t.accessTokenId === accessTokenId) delete
query[AccessToken].filter { t => t.userName == userName && t.accessTokenId == accessTokenId }.delete
})(List((userName, accessTokenId)))
} }

View File

@@ -1,23 +1,20 @@
package gitbucket.core.service package gitbucket.core.service
import java.util.Date import gitbucket.core.model.{GroupMember, Account}
import gitbucket.core.model.{GroupMember, Account, Collaborator, Repository}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.{StringUtil, LDAPUtil} import gitbucket.core.util.{StringUtil, LDAPUtil}
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import profile.simple._ import profile.simple._
import StringUtil._ import StringUtil._
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
// TODO Why is direct import required?
import gitbucket.core.servlet.Database._ import gitbucket.core.model.Profile.dateColumnType
import io.getquill._
trait AccountService { trait AccountService {
private val logger = LoggerFactory.getLogger(classOf[AccountService]) private val logger = LoggerFactory.getLogger(classOf[AccountService])
def authenticate(settings: SystemSettings, userName: String, password: String): Option[Account] = def authenticate(settings: SystemSettings, userName: String, password: String)(implicit s: Session): Option[Account] =
if(settings.ldapAuthentication){ if(settings.ldapAuthentication){
ldapAuthentication(settings, userName, password) ldapAuthentication(settings, userName, password)
} else { } else {
@@ -27,21 +24,22 @@ trait AccountService {
/** /**
* Authenticate by internal database. * Authenticate by internal database.
*/ */
private def defaultAuthentication(userName: String, password: String) = { private def defaultAuthentication(userName: String, password: String)(implicit s: Session) = {
getAccountByUserName(userName).collect { getAccountByUserName(userName).collect {
case account if(!account.groupAccount && account.password == sha1(password)) => Some(account) case account if(!account.isGroupAccount && account.password == sha1(password)) => Some(account)
} getOrElse None } getOrElse None
} }
/** /**
* Authenticate by LDAP. * Authenticate by LDAP.
*/ */
private def ldapAuthentication(settings: SystemSettings, userName: String, password: String): Option[Account] = { private def ldapAuthentication(settings: SystemSettings, userName: String, password: String)
(implicit s: Session): Option[Account] = {
LDAPUtil.authenticate(settings.ldap.get, userName, password) match { LDAPUtil.authenticate(settings.ldap.get, userName, password) match {
case Right(ldapUserInfo) => { case Right(ldapUserInfo) => {
// Create or update account by LDAP information // Create or update account by LDAP information
getAccountByUserName(ldapUserInfo.userName, true) match { getAccountByUserName(ldapUserInfo.userName, true) match {
case Some(x) if(!x.removed) => { case Some(x) if(!x.isRemoved) => {
if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) { if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) {
updateAccount(x.copy(fullName = ldapUserInfo.fullName)) updateAccount(x.copy(fullName = ldapUserInfo.fullName))
} else { } else {
@@ -49,16 +47,16 @@ trait AccountService {
} }
getAccountByUserName(ldapUserInfo.userName) getAccountByUserName(ldapUserInfo.userName)
} }
case Some(x) if(x.removed) => { case Some(x) if(x.isRemoved) => {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.") logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password) defaultAuthentication(userName, password)
} }
case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match {
case Some(x) if(!x.removed) => { case Some(x) if(!x.isRemoved) => {
updateAccount(x.copy(fullName = ldapUserInfo.fullName)) updateAccount(x.copy(fullName = ldapUserInfo.fullName))
getAccountByUserName(ldapUserInfo.userName) getAccountByUserName(ldapUserInfo.userName)
} }
case Some(x) if(x.removed) => { case Some(x) if(x.isRemoved) => {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.") logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password) defaultAuthentication(userName, password)
} }
@@ -76,163 +74,113 @@ trait AccountService {
} }
} }
def getAccountByUserName(userName: String, includeRemoved: Boolean = false): Option[Account] = { def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
db.run(quote { (userName: String, includeRemoved: Boolean) => Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
query[Account].filter { t =>
if(includeRemoved){
t.userName == userName
} else {
t.userName == userName && t.removed == false
}
}
})(userName, includeRemoved).headOption
}
def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false)(implicit s: Session): Map[String, Account] = {
def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false): Map[String, Account] = {
val map = knowns.map(a => a.userName -> a).toMap val map = knowns.map(a => a.userName -> a).toMap
val needs = userNames -- map.keySet val needs = userNames -- map.keySet
if(needs.isEmpty){ if(needs.isEmpty){
map map
} else { }else{
map ++ db.run(quote { (userNames: Set[String]) => map ++ Accounts.filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved)).list.map(a => a.userName -> a).toMap
query[Account].filter { t => userNames.contains(t.userName) && t.removed == false }
})(userNames.toSet).map { a => a.userName -> a }.toMap
} }
} }
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false): Option[Account] = { def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
db.run(quote { (mailAddress: String, includeRemoved: Boolean) => Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
query[Account].filter { t =>
if(includeRemoved){
t.mailAddress.toLowerCase == mailAddress.toLowerCase
} else {
t.mailAddress.toLowerCase == mailAddress.toLowerCase && t.removed == false
}
}
})(mailAddress, includeRemoved).headOption
}
def getAllUsers(includeRemoved: Boolean = true): List[Account] = { def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] =
db.run( if(includeRemoved){
if(includeRemoved){ Accounts sortBy(_.userName) list
quote { query[Account].sortBy(_.userName) } } else {
} else { Accounts filter (_.removed === false.bind) sortBy(_.userName) list
quote { query[Account].filter(_.removed == false).sortBy(_.userName) } }
}
)
}
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String]): Unit = { def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
db.run(quote { query[Account].insert })(Account( (implicit s: Session): Unit =
Accounts insert Account(
userName = userName, userName = userName,
password = password, password = password,
fullName = fullName, fullName = fullName,
mailAddress = mailAddress, mailAddress = mailAddress,
administrator = isAdmin, isAdmin = isAdmin,
url = url, url = url,
registeredDate = currentDate, registeredDate = currentDate,
updatedDate = currentDate, updatedDate = currentDate,
lastLoginDate = None, lastLoginDate = None,
image = None, image = None,
groupAccount = false, isGroupAccount = false,
removed = false isRemoved = false)
))
}
def updateAccount(account: Account): Unit = { def updateAccount(account: Account)(implicit s: Session): Unit =
db.run(quote { (userName: String, password: String, fullName: String, mailAddress: String, administrator: Boolean, Accounts
url: Option[String], registeredDate: Date, updatedDate: Date, lastLoginDate: Option[Date], removed: Boolean) => .filter { a => a.userName === account.userName.bind }
query[Account].filter(_.userName == userName).update( .map { a => (a.password, a.fullName, a.mailAddress, a.isAdmin, a.url.?, a.registeredDate, a.updatedDate, a.lastLoginDate.?, a.removed) }
_.password -> password, .update (
_.fullName -> fullName, account.password,
_.mailAddress -> mailAddress, account.fullName,
_.administrator -> administrator, account.mailAddress,
_.url -> url, account.isAdmin,
_.registeredDate -> registeredDate, account.url,
_.updatedDate -> updatedDate, account.registeredDate,
_.lastLoginDate -> lastLoginDate, currentDate,
_.removed -> removed account.lastLoginDate,
) account.isRemoved)
})((
account.userName,
account.password,
account.fullName,
account.mailAddress,
account.administrator,
account.url,
account.registeredDate,
currentDate,
account.lastLoginDate,
account.removed
))
}
def updateAvatarImage(userName: String, image: Option[String]): Unit = { def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit =
db.run(quote { (userName: String, image: Option[String]) => Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image)
query[Account].filter(_.userName == userName).update(_.image -> image)
})((userName, image))
}
def updateLastLoginDate(userName: String): Unit = { def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
db.run(quote { (userName: String, lastLoginDate: Option[Date]) => Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate)
query[Account].filter(_.userName == userName).update(_.lastLoginDate -> lastLoginDate)
})((userName, Some(currentDate)))
}
def createGroup(groupName: String, url: Option[String]): Unit = { def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit =
db.run( quote { query[Account].insert })(List(Account( Accounts insert Account(
userName = groupName, userName = groupName,
password = "", password = "",
fullName = groupName, fullName = groupName,
mailAddress = groupName + "@devnull", mailAddress = groupName + "@devnull",
administrator = false, isAdmin = false,
url = url, url = url,
registeredDate = currentDate, registeredDate = currentDate,
updatedDate = currentDate, updatedDate = currentDate,
lastLoginDate = None, lastLoginDate = None,
image = None, image = None,
groupAccount = true, isGroupAccount = true,
removed = false isRemoved = false)
)))
}
def updateGroup(groupName: String, url: Option[String], removed: Boolean): Unit = { def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit =
db.run(quote { (groupName: String, url: Option[String], removed: Boolean) => Accounts.filter(_.userName === groupName.bind).map(t => t.url.? -> t.removed).update(url, removed)
query[Account].filter(_.userName == groupName).update(_.url -> url, _.removed -> removed)
})(List((groupName, url, removed)))
}
def updateGroupMembers(groupName: String, members: List[(String, Boolean)]): Unit = {
db.run(quote { (groupName: String) => query[GroupMember].filter(_.groupName == groupName).delete })(groupName)
def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
GroupMembers.filter(_.groupName === groupName.bind).delete
members.foreach { case (userName, isManager) => members.foreach { case (userName, isManager) =>
db.run(quote { query[GroupMember].insert })(GroupMember(groupName, userName, isManager)) GroupMembers insert GroupMember (groupName, userName, isManager)
} }
} }
def getGroupMembers(groupName: String): List[GroupMember] = { def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] =
db.run(quote { (groupName: String) => GroupMembers
query[GroupMember].filter(_.groupName == groupName).sortBy(_.userName) .filter(_.groupName === groupName.bind)
})(groupName) .sortBy(_.userName)
.list
def getGroupsByUserName(userName: String)(implicit s: Session): List[String] =
GroupMembers
.filter(_.userName === userName.bind)
.sortBy(_.groupName)
.map(_.groupName)
.list
def removeUserRelatedData(userName: String)(implicit s: Session): Unit = {
GroupMembers.filter(_.userName === userName.bind).delete
Collaborators.filter(_.collaboratorName === userName.bind).delete
Repositories.filter(_.userName === userName.bind).delete
} }
def getGroupsByUserName(userName: String): List[String] = { def getGroupNames(userName: String)(implicit s: Session): List[String] = {
db.run(quote { (userName: String) => List(userName) ++
query[GroupMember].filter(_.userName == userName).sortBy(_.groupName).map(_.groupName) Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list
})(userName)
}
def removeUserRelatedData(userName: String): Unit = {
db.run(quote { (userName: String) => query[GroupMember].filter(_.userName == userName).delete })(userName)
db.run(quote { (userName: String) => query[Collaborator].filter(_.collaboratorName == userName).delete })(userName)
db.run(quote { (userName: String) => query[Repository].filter(_.userName == userName).delete })(userName)
}
def getGroupNames(userName: String): List[String] = {
List(userName) ++ db.run(quote { (userName: String) =>
query[Collaborator].filter(_.collaboratorName == userName).sortBy(_.userName).map(_.userName)
})(userName)
} }
} }

View File

@@ -1,180 +1,194 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model._ import gitbucket.core.model.Activity
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
import profile.simple._ import profile.simple._
import gitbucket.core.servlet.Database._
import io.getquill._
trait ActivityService { trait ActivityService {
def deleteOldActivities(limit: Int): Int = def deleteOldActivities(limit: Int)(implicit s: Session): Int = {
db.run (quote { (limit: Int) => Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id =>
query[Activity].map(_.activityId).sortBy(x => x)(Ord.desc).drop(limit) Activities.filter(_.activityId <= id.bind).delete
})(limit).headOption.map { activityId =>
db.run (
quote { (activityId: Int) => query[Activity].filter(_.activityId <= activityId).delete }
)(activityId)
} getOrElse 0 } getOrElse 0
}
def getActivitiesByUser(activityUserName: String, isPublic: Boolean): List[Activity] = def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] =
db.run(quote { (activityUserName: String, isPublic: Boolean) => Activities
query[Activity].join(query[Repository]).on((a, r) => a.userName == r.userName && a.repositoryName == r.repositoryName) .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter { case (a, r) => .filter { case (t1, t2) =>
if(isPublic){ if(isPublic){
a.activityUserName == activityUserName (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind)
} else { } else {
a.activityUserName == activityUserName && r.`private` == false (t1.activityUserName === activityUserName.bind)
}
} }
.sortBy { case (a, r) => a.activityId }(Ord.desc) }
.map { case (a, r) => a } .sortBy { case (t1, t2) => t1.activityId desc }
.take(30) .map { case (t1, t2) => t1 }
})(activityUserName, isPublic) .take(30)
.list
def getRecentActivities(): List[Activity] = def getRecentActivities()(implicit s: Session): List[Activity] =
db.run(quote { Activities
query[Activity].join(query[Repository]).on((a, r) => a.userName == r.userName && a.repositoryName == r.repositoryName) .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter { case (a, r) => r.`private` == false} .filter { case (t1, t2) => t2.isPrivate === false.bind }
.sortBy { case (a, r) => a.activityId }(Ord.desc) .sortBy { case (t1, t2) => t1.activityId desc }
.map { case (a, r) => a } .map { case (t1, t2) => t1 }
.take(30) .take(30)
}) .list
def getRecentActivitiesByOwners(owners : Set[String]): List[Activity] = def getRecentActivitiesByOwners(owners : Set[String])(implicit s: Session): List[Activity] =
db.run(quote { (owners: Set[String]) => Activities
query[Activity].join(query[Repository]).on((a, r) => a.userName == r.userName && a.repositoryName == r.repositoryName) .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter { case (a, r) => r.`private` == false || owners.contains(r.userName) } .filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) }
.sortBy { case (a, r) => a.activityId }(Ord.desc) .sortBy { case (t1, t2) => t1.activityId desc }
.map { case (a, r) => a } .map { case (t1, t2) => t1 }
.take(30) .take(30)
})(owners) .list
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit = def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"create_repository", "create_repository",
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
None) None,
currentDate)
def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit = def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"open_issue", "open_issue",
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title)) Some(title),
currentDate)
def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit = def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"close_issue", "close_issue",
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title)) Some(title),
currentDate)
def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit = def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"close_issue", "close_issue",
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title)) Some(title),
currentDate)
def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit = def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"reopen_issue", "reopen_issue",
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title)) Some(title),
currentDate)
def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String): Unit = def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"comment_issue", "comment_issue",
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200))) Some(cut(comment, 200)),
currentDate)
def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String): Unit = def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"comment_issue", "comment_issue",
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200))) Some(cut(comment, 200)),
currentDate)
def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String): Unit = def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"comment_commit", "comment_commit",
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
Some(cut(comment, 200))) Some(cut(comment, 200)),
currentDate
)
def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String): Unit = def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"create_wiki", "create_wiki",
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
Some(pageName)) Some(pageName),
currentDate)
def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String): Unit = def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"edit_wiki", "edit_wiki",
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
Some(pageName + ":" + commitId)) Some(pageName + ":" + commitId),
currentDate)
def recordPushActivity(userName: String, repositoryName: String, activityUserName: String, def recordPushActivity(userName: String, repositoryName: String, activityUserName: String,
branchName: String, commits: List[JGitUtil.CommitInfo]): Unit = branchName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
insertActivity(userName, repositoryName, activityUserName, Activities insert Activity(userName, repositoryName, activityUserName,
"push", "push",
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
Some(commits.map { commit => commit.id + ":" + commit.shortMessage }.mkString("\n"))) Some(commits.map { commit => commit.id + ":" + commit.shortMessage }.mkString("\n")),
currentDate)
def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String, def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String,
tagName: String, commits: List[JGitUtil.CommitInfo]): Unit = tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
insertActivity(userName, repositoryName, activityUserName, Activities insert Activity(userName, repositoryName, activityUserName,
"create_tag", "create_tag",
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
None) None,
currentDate)
def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String, def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String,
tagName: String, commits: List[JGitUtil.CommitInfo]): Unit = tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
insertActivity(userName, repositoryName, activityUserName, Activities insert Activity(userName, repositoryName, activityUserName,
"delete_tag", "delete_tag",
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
None) None,
currentDate)
def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String): Unit = def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"create_branch", "create_branch",
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
None) None,
currentDate)
def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String): Unit = def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"delete_branch", "delete_branch",
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
None) None,
currentDate)
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String): Unit = def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(implicit s: Session): Unit =
insertActivity(userName, repositoryName, activityUserName, Activities insert Activity(userName, repositoryName, activityUserName,
"fork", "fork",
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]", s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
None) None,
currentDate)
def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit = def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"open_pullreq", "open_pullreq",
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title)) Some(title),
currentDate)
def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String): Unit = def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String)
insertActivity(userName, repositoryName, activityUserName, (implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"merge_pullreq", "merge_pullreq",
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(message)) Some(message),
currentDate)
private def insertActivity(userName: String, repositoryName: String, activityUserName: String, activityType: String,
message: String, additionalInfo: Option[String]): Unit = {
db.run(quote { query[Activity].insert })(Activity(
userName = userName,
repositoryName = repositoryName,
activityUserName = activityUserName,
activityType = activityType,
message = message,
additionalInfo = additionalInfo,
activityDate = currentDate
))
}
private def cut(value: String, length: Int): String = private def cut(value: String, length: Int): String =
if(value.length > length) value.substring(0, length) + "..." else value if(value.length > length) value.substring(0, length) + "..." else value

View File

@@ -1,34 +1,35 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.CommitComment import gitbucket.core.model.CommitComment
import gitbucket.core.util.{StringUtil, Implicits}
import scala.slick.jdbc.{StaticQuery => Q}
import Q.interpolation
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import profile.simple._ import profile.simple._
import Implicits._
import gitbucket.core.servlet.Database._ import StringUtil._
import io.getquill._
trait CommitsService { trait CommitsService {
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean) = def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(implicit s: Session) =
db.run(quote { (owner: String, repository: String, commitId: String, includePullRequest: Boolean) => CommitComments filter {
query[CommitComment].filter { t => t => t.byCommit(owner, repository, commitId) && (t.issueId.isEmpty || includePullRequest)
t.userName == owner && t.repositoryName == repository && t.commitId == commitId && (t.issueId.isEmpty || includePullRequest) } list
}
})(owner, repository, commitId, includePullRequest)
def getCommitComment(owner: String, repository: String, commentId: String) = def getCommitComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
if (commentId forall (_.isDigit)) if (commentId forall (_.isDigit))
db.run(quote { (owner: String, repository: String, commentId: Int) => CommitComments filter { t =>
query[CommitComment].filter(t => t.userName == owner && t.repositoryName == repository && t.commentId == commentId) t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
})(owner, repository, commentId.toInt).headOption } firstOption
else else
None None
def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String, def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String,
content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int], content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int],
issueId: Option[Int])(implicit s: Session): Int = issueId: Option[Int])(implicit s: Session): Int =
CommitComments.autoInc insert CommitComment( // TODO Remain Slick code CommitComments.autoInc insert CommitComment(
userName = owner, userName = owner,
repositoryName = repository, repositoryName = repository,
commitId = commitId, commitId = commitId,
@@ -41,12 +42,13 @@ trait CommitsService {
updatedDate = currentDate, updatedDate = currentDate,
issueId = issueId) issueId = issueId)
def updateCommitComment(commentId: Int, content: String) = def updateCommitComment(commentId: Int, content: String)(implicit s: Session) =
db.run(quote { (commentId: Int, content: String, updatedDate: java.util.Date) => CommitComments
query[CommitComment].filter(_.commentId == commentId).update(_.content -> content, _.updatedDate -> updatedDate) .filter (_.byPrimaryKey(commentId))
})(commentId, content, currentDate) .map { t =>
t.content -> t.updatedDate
def deleteCommitComment(commentId: Int) = }.update (content, currentDate)
db.run(quote { (commentId: Int) => query[CommitComment].filter(_.commentId == commentId).delete })(commentId)
def deleteCommitComment(commentId: Int)(implicit s: Session) =
CommitComments filter (_.byPrimaryKey(commentId)) delete
} }

View File

@@ -76,7 +76,7 @@ object ProtectedBranchService {
includeAdministrators: Boolean) extends AccountService with CommitStatusService { includeAdministrators: Boolean) extends AccountService with CommitStatusService {
def isAdministrator(pusher: String)(implicit session: Session): Boolean = def isAdministrator(pusher: String)(implicit session: Session): Boolean =
pusher == owner || getGroupMembers(owner).filter(gm => gm.userName == pusher && gm.manager).nonEmpty pusher == owner || getGroupMembers(owner).filter(gm => gm.userName == pusher && gm.isManager).nonEmpty
/** /**
* Can't be force pushed * Can't be force pushed

View File

@@ -22,7 +22,7 @@ trait RepositoryCreationService {
insertRepository(name, owner, description, isPrivate) insertRepository(name, owner, description, isPrivate)
// Add collaborators for group repository // Add collaborators for group repository
if(ownerAccount.groupAccount){ if(ownerAccount.isGroupAccount){
getGroupMembers(owner).foreach { member => getGroupMembers(owner).foreach { member =>
addCollaborator(owner, name, member.userName) addCollaborator(owner, name, member.userName)
} }

View File

@@ -53,7 +53,30 @@ trait RepositorySearchService { self: IssuesService =>
} }
} }
private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = { def countWikiPages(owner: String, repository: String, query: String): Int =
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
}
def searchWikiPages(owner: String, repository: String, query: String): List[FileSearchResult] =
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
if(JGitUtil.isEmpty(git)){
Nil
} else {
val files = searchRepositoryFiles(git, query)
val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD")
files.map { case (path, text) =>
val (highlightText, lineNumber) = getHighlightText(text, query)
FileSearchResult(
path.replaceFirst("\\.md$", ""),
commits(path).getCommitterIdent.getWhen,
highlightText,
lineNumber)
}
}
}
def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
val revWalk = new RevWalk(git.getRepository) val revWalk = new RevWalk(git.getRepository)
val objectId = git.getRepository.resolve("HEAD") val objectId = git.getRepository.resolve("HEAD")
val revCommit = revWalk.parseCommit(objectId) val revCommit = revWalk.parseCommit(objectId)

View File

@@ -27,7 +27,7 @@ trait RepositoryService { self: AccountService =>
Repository( Repository(
userName = userName, userName = userName,
repositoryName = repositoryName, repositoryName = repositoryName,
`private` = isPrivate, isPrivate = isPrivate,
description = description, description = description,
defaultBranch = "master", defaultBranch = "master",
registeredDate = currentDate, registeredDate = currentDate,
@@ -115,7 +115,7 @@ trait RepositoryService { self: AccountService =>
repositoryName = newRepositoryName repositoryName = newRepositoryName
)) :_*) )) :_*)
if(account.groupAccount){ if(account.isGroupAccount){
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*) Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
} else { } else {
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
@@ -270,9 +270,9 @@ trait RepositoryService { self: AccountService =>
(implicit s: Session): List[RepositoryInfo] = { (implicit s: Session): List[RepositoryInfo] = {
(loginAccount match { (loginAccount match {
// for Administrators // for Administrators
case Some(x) if(x.administrator) => Repositories case Some(x) if(x.isAdmin) => Repositories
// for Normal Users // for Normal Users
case Some(x) if(!x.administrator) => case Some(x) if(!x.isAdmin) =>
Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) || Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) ||
(Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists) (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists)
} }
@@ -297,8 +297,8 @@ trait RepositoryService { self: AccountService =>
} }
private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] = private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] =
if(getAccountByUserName(userName).exists(_.groupAccount)){ if(getAccountByUserName(userName).exists(_.isGroupAccount)){
getGroupMembers(userName).collect { case x if(x.manager) => x.userName } getGroupMembers(userName).collect { case x if(x.isManager) => x.userName }
} else { } else {
Seq(userName) Seq(userName)
} }
@@ -365,7 +365,7 @@ trait RepositoryService { self: AccountService =>
def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
loginAccount match { loginAccount match {
case Some(a) if(a.administrator) => true case Some(a) if(a.isAdmin) => true
case Some(a) if(a.userName == owner) => true case Some(a) if(a.userName == owner) => true
case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true
case _ => false case _ => false

View File

@@ -1,7 +1,6 @@
package gitbucket.core.service package gitbucket.core.service
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import fr.brouillard.oss.security.xhub.XHub import fr.brouillard.oss.security.xhub.XHub
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter} import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
import gitbucket.core.api._ import gitbucket.core.api._
@@ -12,7 +11,6 @@ import profile.simple._
import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.RepositoryName import gitbucket.core.util.RepositoryName
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import org.apache.http.NameValuePair import org.apache.http.NameValuePair
import org.apache.http.client.entity.UrlEncodedFormEntity import org.apache.http.client.entity.UrlEncodedFormEntity
import org.apache.http.message.BasicNameValuePair import org.apache.http.message.BasicNameValuePair
@@ -22,6 +20,8 @@ import org.slf4j.LoggerFactory
import scala.concurrent._ import scala.concurrent._
import org.apache.http.HttpRequest import org.apache.http.HttpRequest
import org.apache.http.HttpResponse import org.apache.http.HttpResponse
import gitbucket.core.model.WebHookContentType
import org.apache.http.client.entity.EntityBuilder
trait WebHookService { trait WebHookService {
@@ -52,15 +52,15 @@ trait WebHookService {
.map{ case (w,t) => w -> t.event } .map{ case (w,t) => w -> t.event }
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption .list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = { def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
WebHooks insert WebHook(owner, repository, url, token) WebHooks insert WebHook(owner, repository, url, ctype, token)
events.toSet.map{ event: WebHook.Event => events.toSet.map{ event: WebHook.Event =>
WebHookEvents insert WebHookEvent(owner, repository, url, event) WebHookEvents insert WebHookEvent(owner, repository, url, event)
} }
} }
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = { def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => w.token).update(token) WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token))
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
events.toSet.map{ event: WebHook.Event => events.toSet.map{ event: WebHook.Event =>
WebHookEvents insert WebHookEvent(owner, repository, url, event) WebHookEvents insert WebHookEvent(owner, repository, url, event)
@@ -100,19 +100,29 @@ trait WebHookService {
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
logger.debug(s"start web hook invocation for ${webHook.url}") logger.debug(s"start web hook invocation for ${webHook.url}")
val httpPost = new HttpPost(webHook.url) val httpPost = new HttpPost(webHook.url)
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded") logger.info(s"Content-Type: ${webHook.ctype.ctype}")
httpPost.addHeader("Content-Type", webHook.ctype.ctype)
httpPost.addHeader("X-Github-Event", event.name) httpPost.addHeader("X-Github-Event", event.name)
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString) httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
val params: java.util.List[NameValuePair] = new java.util.ArrayList() webHook.ctype match {
params.add(new BasicNameValuePair("payload", json)) case WebHookContentType.FORM => {
def postContent = new UrlEncodedFormEntity(params, "UTF-8") val params: java.util.List[NameValuePair] = new java.util.ArrayList()
httpPost.setEntity(postContent) params.add(new BasicNameValuePair("payload", json))
def postContent = new UrlEncodedFormEntity(params, "UTF-8")
if (!webHook.token.isEmpty) { httpPost.setEntity(postContent)
// TODO find a better way and see how to extract content from postContent if (webHook.token.exists(_.trim.nonEmpty)) {
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8") // TODO find a better way and see how to extract content from postContent
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, contentAsBytes)) val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.get, contentAsBytes))
}
}
case WebHookContentType.JSON => {
httpPost.setEntity(EntityBuilder.create().setText(json).build())
if (!webHook.token.isEmpty) {
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8")))
}
}
} }
val res = httpClient.execute(httpPost) val res = httpClient.execute(httpPost)

View File

@@ -21,6 +21,7 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current GitBucket version. * The history of versions. A head of this sequence is the current GitBucket version.
*/ */
val versions = Seq( val versions = Seq(
new Version(3, 14),
new Version(3, 13), new Version(3, 13),
new Version(3, 12), new Version(3, 12),
new Version(3, 11), new Version(3, 11),

View File

@@ -76,14 +76,14 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
case Array(_, repositoryOwner, repositoryName, _*) => case Array(_, repositoryOwner, repositoryName, _*) =>
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match { getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match {
case Some(repository) => { case Some(repository) => {
if(!isUpdating && !repository.repository.`private` && settings.allowAnonymousAccess){ if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
chain.doFilter(request, response) chain.doFilter(request, response)
} else { } else {
val passed = for { val passed = for {
auth <- Option(request.getHeader("Authorization")) auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2) Array(username, password) = decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password) account <- authenticate(settings, username, password)
} yield if(isUpdating || repository.repository.`private`){ } yield if(isUpdating || repository.repository.isPrivate){
if(hasWritePermission(repository.owner, repository.name, Some(account))){ if(hasWritePermission(repository.owner, repository.name, Some(account))){
request.setAttribute(Keys.Request.UserName, account.userName) request.setAttribute(Keys.Request.UserName, account.userName)
true true

View File

@@ -4,14 +4,10 @@ import javax.servlet._
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
import com.mchange.v2.c3p0.ComboPooledDataSource import com.mchange.v2.c3p0.ComboPooledDataSource
import gitbucket.core.util.DatabaseConfig import gitbucket.core.util.DatabaseConfig
import io.getquill._
import io.getquill.naming.SnakeCase
import io.getquill.sources.sql.idiom.H2Dialect
import org.scalatra.ScalatraBase import org.scalatra.ScalatraBase
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import slick.jdbc.JdbcBackend.{Database => SlickDatabase, Session} import slick.jdbc.JdbcBackend.{Database => SlickDatabase, Session}
import gitbucket.core.util.Keys import gitbucket.core.util.Keys
import Database._
/** /**
* Controls the transaction with the open session in view pattern. * Controls the transaction with the open session in view pattern.
@@ -29,20 +25,17 @@ class TransactionFilter extends Filter {
// assets don't need transaction // assets don't need transaction
chain.doFilter(req, res) chain.doFilter(req, res)
} else { } else {
db.transaction { Database() withTransaction { session =>
// TODO Delete after moving to quill // Register Scalatra error callback to rollback transaction
Database() withTransaction { session => ScalatraBase.onFailure { _ =>
// Register Scalatra error callback to rollback transaction logger.debug("Rolled back transaction")
ScalatraBase.onFailure { _ => session.rollback()
logger.debug("Rolled back transaction") }(req.asInstanceOf[HttpServletRequest])
session.rollback()
}(req.asInstanceOf[HttpServletRequest])
logger.debug("begin transaction") logger.debug("begin transaction")
req.setAttribute(Keys.Request.DBSession, session) req.setAttribute(Keys.Request.DBSession, session)
chain.doFilter(req, res) chain.doFilter(req, res)
logger.debug("end transaction") logger.debug("end transaction")
}
} }
} }
} }
@@ -53,9 +46,6 @@ object Database {
private val logger = LoggerFactory.getLogger(Database.getClass) private val logger = LoggerFactory.getLogger(Database.getClass)
lazy val db = source(new JdbcSourceConfig[H2Dialect, SnakeCase]("db"))
// TODO Delete after moving to quill
private val dataSource: ComboPooledDataSource = { private val dataSource: ComboPooledDataSource = {
val ds = new ComboPooledDataSource val ds = new ComboPooledDataSource
ds.setDriverClass(DatabaseConfig.driver) ds.setDriverClass(DatabaseConfig.driver)
@@ -66,19 +56,15 @@ object Database {
ds ds
} }
// TODO Delete after moving to quill private val db: SlickDatabase = {
private val slickDatabase: SlickDatabase = {
SlickDatabase.forDataSource(dataSource) SlickDatabase.forDataSource(dataSource)
} }
// TODO Delete after moving to quill def apply(): SlickDatabase = db
def apply(): SlickDatabase = slickDatabase
// TODO Delete after moving to quill
def getSession(req: ServletRequest): Session = def getSession(req: ServletRequest): Session =
req.getAttribute(Keys.Request.DBSession).asInstanceOf[Session] req.getAttribute(Keys.Request.DBSession).asInstanceOf[Session]
// TODO Delete after moving to quill
def closeDataSource(): Unit = dataSource.close def closeDataSource(): Unit = dataSource.close
} }

View File

@@ -92,7 +92,7 @@ class DefaultGitUploadPack(owner: String, repoName: String) extends DefaultGitCo
override protected def runTask(user: String)(implicit session: Session): Unit = { override protected def runTask(user: String)(implicit session: Session): Unit = {
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo => getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo =>
if(!repositoryInfo.repository.`private` || isWritableUser(user, repositoryInfo)){ if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){
using(Git.open(getRepositoryDir(owner, repoName))) { git => using(Git.open(getRepositoryDir(owner, repoName))) { git =>
val repository = git.getRepository val repository = git.getRepository
val upload = new UploadPack(repository) val upload = new UploadPack(repository)

View File

@@ -17,7 +17,7 @@ trait OneselfAuthenticator { self: ControllerBase =>
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.administrator) => action case Some(x) if(x.isAdmin) => action
case Some(x) if(paths(0) == x.userName) => action case Some(x) if(paths(0) == x.userName) => action
case _ => Unauthorized() case _ => Unauthorized()
} }
@@ -38,10 +38,10 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.administrator) => action(repository) case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(repository.owner == x.userName) => action(repository) case Some(x) if(repository.owner == x.userName) => action(repository)
case Some(x) if(getGroupMembers(repository.owner).exists { member => case Some(x) if(getGroupMembers(repository.owner).exists { member =>
member.userName == x.userName && member.manager == true member.userName == x.userName && member.isManager == true
}) => action(repository) }) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
} }
@@ -78,7 +78,7 @@ trait AdminAuthenticator { self: ControllerBase =>
private def authenticate(action: => Any) = { private def authenticate(action: => Any) = {
{ {
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.administrator) => action case Some(x) if(x.isAdmin) => action
case _ => Unauthorized() case _ => Unauthorized()
} }
} }
@@ -97,7 +97,7 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.administrator) => action(repository) case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(paths(0) == x.userName) => action(repository) case Some(x) if(paths(0) == x.userName) => action(repository)
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
@@ -119,11 +119,11 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
if(!repository.repository.`private`){ if(!repository.repository.isPrivate){
action(repository) action(repository)
} else { } else {
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.administrator) => action(repository) case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(paths(0) == x.userName) => action(repository) case Some(x) if(paths(0) == x.userName) => action(repository)
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
@@ -147,8 +147,8 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.administrator) => action(repository) case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(!repository.repository.`private`) => action(repository) case Some(x) if(!repository.repository.isPrivate) => action(repository)
case Some(x) if(paths(0) == x.userName) => action(repository) case Some(x) if(paths(0) == x.userName) => action(repository)
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
@@ -171,7 +171,7 @@ trait GroupManagerAuthenticator { self: ControllerBase with AccountService =>
defining(request.paths){ paths => defining(request.paths){ paths =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(getGroupMembers(paths(0)).exists { member => case Some(x) if(getGroupMembers(paths(0)).exists { member =>
member.userName == x.userName && member.manager member.userName == x.userName && member.isManager
}) => action }) => action
case _ => Unauthorized() case _ => Unauthorized()
} }

View File

@@ -30,7 +30,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
) )
.distinct .distinct
.withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded .withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
.foreach ( getAccountByUserName(_) filterNot (_.groupAccount) filterNot (LDAPUtil.isDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) ) .foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) filterNot (LDAPUtil.isDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) )
} }

View File

@@ -5,56 +5,51 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main("Applications"){ @html.main("Applications"){
<div class="container body"> <div class="container body">
<div class="row"> @menu("application", settings.ssh){
<div class="col-md-3"> <div class="panel panel-default">
@menu("application", settings.ssh) <div class="panel-heading strong">Personal access tokens</div>
</div> <div class="panel-body">
<div class="col-md-9"> @if(personalTokens.isEmpty && gneratedToken.isEmpty){
<div class="panel panel-default"> No tokens.
<div class="panel-heading strong">Personal access tokens</div> } else {
<div class="panel-body"> Tokens you have generated that can be used to access the GitBucket API.
@if(personalTokens.isEmpty && gneratedToken.isEmpty){ <hr style="margin-top: 10px;">
No tokens. }
} else { @gneratedToken.map{ case (token, tokenString) =>
Tokens you have generated that can be used to access the GitBucket API. <div class="alert alert-info">
<hr style="margin-top: 10px;"> Make sure to copy your new personal access token now. You won't be able to see it again!
} </div>
@gneratedToken.map{ case (token, tokenString) => <a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
<div class="alert alert-info"> <div style="width: 50%;">
Make sure to copy your new personal access token now. You won't be able to see it again! @helper.html.copy("generated-token-copy", tokenString){
</div> <input type="text" value="@tokenString" class="form-control input-sm" readonly>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
<div style="width: 50%;">
@helper.html.copy("generated-token-copy", tokenString){
<input type="text" value="@tokenString" class="form-control input-sm" readonly>
}
</div>
<hr style="margin-top: 10px;">
}
@personalTokens.zipWithIndex.map { case (token, i) =>
@if(i != 0){
<hr style="margin-top: 10px;">
} }
<strong>@token.note</strong> </div>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a> <hr style="margin-top: 10px;">
}
@personalTokens.zipWithIndex.map { case (token, i) =>
@if(i != 0){
<hr>
} }
<strong style="line-height: 30px;">@token.note</strong>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
}
</div>
</div>
<form method="POST" action="@path/@account.userName/_personalToken" validate="true">
<div class="panel panel-default">
<div class="panel-heading strong">Generate new token</div>
<div class="panel-body">
<fieldset>
<label for="note" class="strong">Token description</label>
<div><span id="error-note" class="error"></span></div>
<input type="text" name="note" id="note" class="form-control"/>
<p class="muted">What's this token for?</p>
</fieldset>
<input type="submit" class="btn btn-success" value="Generate token"/>
</div> </div>
</div> </div>
<form method="POST" action="@path/@account.userName/_personalToken" validate="true"> </form>
<div class="panel panel-default"> }
<div class="panel-heading strong">Generate new token</div>
<div class="panel-body">
<fieldset>
<label for="note" class="strong">Token description</label>
<div><span id="error-note" class="error"></span></div>
<input type="text" name="note" id="note" class="form-control"/>
<p class="muted">What's this token for?</p>
</fieldset>
<input type="submit" class="btn btn-success" value="Generate token"/>
</div>
</div>
</form>
</div>
</div>
</div> </div>
} }

View File

@@ -4,14 +4,10 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main("Edit your profile"){ @html.main("Edit your profile"){
<div class="container body"> <div class="container body">
<div class="row"> @menu("profile", settings.ssh){
<div class="col-md-3"> @helper.html.information(info)
@menu("profile", settings.ssh) @if(LDAPUtil.isDummyMailAddress(account)){<div class="alert alert-danger">Please register your mail address.</div>}
</div> <form action="@url(account.userName)/_edit" method="POST" validate="true">
<div class="col-md-9">
@helper.html.information(info)
@if(LDAPUtil.isDummyMailAddress(account)){<div class="alert alert-danger">Please register your mail address.</div>}
<form action="@url(account.userName)/_edit" method="POST" validate="true">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading strong">Profile</div> <div class="panel-heading strong">Profile</div>
<div class="panel-body"> <div class="panel-body">
@@ -49,17 +45,17 @@
</fieldset> </fieldset>
</div> </div>
</div> </div>
<div style="margin-top: 20px;">
<div class="pull-right">
<a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
</div>
<input type="submit" class="btn btn-success" value="Save"/>
@if(!LDAPUtil.isDummyMailAddress(account)){<a href="@url(account.userName)" class="btn btn-default">Cancel</a>}
</div>
</div> </div>
</div> </div>
<div>
<div class="pull-right">
<a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
</div>
<input type="submit" class="btn btn-success" value="Save"/>
@if(!LDAPUtil.isDummyMailAddress(account)){<a href="@url(account.userName)" class="btn btn-default">Cancel</a>}
</div>
</form> </form>
</div> }
</div> </div>
} }
<script> <script>

View File

@@ -2,7 +2,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(if(account.isEmpty) "Create group" else "Edit group"){ @html.main(if(account.isEmpty) "Create group" else "Edit group"){
<div class="container body"> <div class="body main-center">
<form id="form" method="post" action="@if(account.isEmpty){@path/groups/new} else {@path/@account.get.userName/_editgroup}" validate="true"> <form id="form" method="post" action="@if(account.isEmpty){@path/groups/new} else {@path/@account.get.userName/_editgroup}" validate="true">
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
@@ -32,7 +32,7 @@
</ul> </ul>
@helper.html.account("memberName", 200) @helper.html.account("memberName", 200)
<input type="button" class="btn btn-default" value="Add" id="addMember"/> <input type="button" class="btn btn-default" value="Add" id="addMember"/>
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.manager).mkString(",")"/> <input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/>
<div> <div>
<span class="error" id="error-members"></span> <span class="error" id="error-members"></span>
</div> </div>
@@ -42,12 +42,12 @@
<fieldset class="margin"> <fieldset class="margin">
@if(account.isDefined){ @if(account.isDefined){
<div class="pull-right"> <div class="pull-right">
<a href="@url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger btn-lg">Delete Group</a> <a href="@url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a>
</div> </div>
} }
<input type="submit" class="btn btn-success btn-lg" value="@if(account.isEmpty){Create Group} else {Update Group}"/> <input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
@if(account.isDefined){ @if(account.isDefined){
<a href="@url(account.get.userName)" class="btn btn-default btn-lg">Cancel</a> <a href="@url(account.get.userName)" class="btn btn-default">Cancel</a>
} }
</fieldset> </fieldset>
</form> </form>
@@ -103,22 +103,22 @@ $(function(){
}); });
@members.map { member => @members.map { member =>
addMemberHTML('@member.userName', @member.manager); addMemberHTML('@member.userName', @member.isManager);
} }
function addMemberHTML(userName, isManager){ function addMemberHTML(userName, isManager){
var memberButton = $('<button type="button" class="btn btn-default btn-mini" value="false">Member</button>').data('name', userName); var memberButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="false" name="' + userName + '">Member</label>');
if(!isManager){ if(!isManager){
memberButton.addClass('active'); memberButton.addClass('active');
} }
var managerButton = $('<button type="button" class="btn btn-default btn-mini" value="true">Manager</button>').data('name', userName); var managerButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="true" name="' + userName + '">Manager</label>');
if(isManager){ if(isManager){
managerButton.addClass('active'); managerButton.addClass('active');
} }
$('#member-list').append($('<li>') $('#member-list').append($('<li>')
.data('name', userName) .data('name', userName)
.append($('<div class="btn-group is_manager" data-toggle="buttons-radio">') .append($('<div class="btn-group is_manager" data-toggle="buttons">')
.append(memberButton) .append(memberButton)
.append(managerButton)) .append(managerButton))
.append(' ') .append(' ')
@@ -130,9 +130,7 @@ $(function(){
function updateMembers(){ function updateMembers(){
var members = $('#member-list li').map(function(i, e){ var members = $('#member-list li').map(function(i, e){
var userName = $(e).data('name'); var userName = $(e).data('name');
return userName + ':' + $('button.active').filter(function(i, e){ return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
return $(e).data('name') == userName;
}).attr('value');
}).get().join(','); }).get().join(',');
$('#members').val(members); $('#members').val(members);
} }

View File

@@ -4,56 +4,56 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(account.userName){ @html.main(account.userName){
<div class="container body"> <div class="container body">
<div class="container-fluid"> <div class="main-sidebar">
<div class="row"> <div class="block">
<div class="col-md-4"> <div class="account-image">@avatar(account.userName, 240)</div>
<div class="block"> <div class="account-fullname">@account.fullName</div>
<div class="account-image">@avatar(account.userName, 270)</div> <div class="account-username">@account.userName</div>
<div class="account-fullname">@account.fullName</div>
<div class="account-username">@account.userName</div>
</div>
<div class="block">
@if(account.url.isDefined){
<div><i class="octicon octicon-home"></i> <a href="@account.url">@account.url</a></div>
}
<div><i class="octicon octicon-clock"></i> <span class="muted">Joined on</span> @date(account.registeredDate)</div>
</div>
@if(groupNames.nonEmpty){
<div>
<div>Groups</div>
@groupNames.map { groupName =>
@avatarLink(groupName, 36, tooltip = true)
}
</div>
}
</div>
<div class="col-md-8">
<ul class="nav nav-tabs" style="margin-bottom: 5px;">
<li@if(active == "repositories"){ class="active"}><a href="@url(account.userName)?tab=repositories">Repositories</a></li>
@if(account.groupAccount){
<li@if(active == "members"){ class="active"}><a href="@url(account.userName)?tab=members">Members</a></li>
} else {
<li@if(active == "activity"){ class="active"}><a href="@url(account.userName)?tab=activity">Public Activity</a></li>
}
@if(loginAccount.isDefined && loginAccount.get.userName == account.userName){
<li class="pull-right">
<div class="button-group">
<a href="@url(account.userName)/_edit" class="btn btn-default">Edit Your Profile</a>
</div>
</li>
}
@if(loginAccount.isDefined && account.groupAccount && isGroupManager){
<li class="pull-right">
<div class="button-groRepiosup">
<a href="@url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a>
</div>
</li>
}
</ul>
@body
</div>
</div> </div>
<div class="block">
@if(account.url.isDefined){
<div><i class="octicon octicon-home"></i> <a href="@account.url">@account.url</a></div>
}
<div><i class="octicon octicon-clock"></i> <span class="muted">Joined on</span> @date(account.registeredDate)</div>
</div>
@if(groupNames.nonEmpty){
<div>
<div>Groups</div>
@groupNames.map { groupName =>
@avatarLink(groupName, 36, tooltip = true)
}
</div>
}
</div>
<div class="main-content">
<ul class="nav nav-tabs" style="margin-bottom: 5px;">
<li@if(active == "repositories"){ class="active"}><a href="@url(account.userName)?tab=repositories">Repositories</a></li>
@if(account.isGroupAccount){
<li@if(active == "members"){ class="active"}><a href="@url(account.userName)?tab=members">Members</a></li>
} else {
<li@if(active == "activity"){ class="active"}><a href="@url(account.userName)?tab=activity">Public Activity</a></li>
}
@gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab =>
@tab(account, context).map { link =>
<li@if(active == link.id){ class="active"}><a href="@path/@link.path">@link.label</a></li>
}
}
@if(loginAccount.isDefined && loginAccount.get.userName == account.userName){
<li class="pull-right">
<div class="button-group">
<a href="@url(account.userName)/_edit" class="btn btn-default">Edit Your Profile</a>
</div>
</li>
}
@if(loginAccount.isDefined && account.isGroupAccount && isGroupManager){
<li class="pull-right">
<div class="button-group">
<a href="@url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a>
</div>
</li>
}
</ul>
@body
</div> </div>
</div> </div>
} }

View File

@@ -1,7 +1,7 @@
@(active: String, ssh: Boolean)(implicit context: gitbucket.core.controller.Context) @(active: String, ssh: Boolean)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
<div class="box"> <div class="main-sidebar">
<ul class="nav nav-tabs nav-stacked side-menu"> <ul class="nav nav-pills nav-stacked">
<li@if(active=="profile"){ class="active"}> <li@if(active=="profile"){ class="active"}>
<a href="@path/@loginAccount.get.userName/_edit">Profile</a> <a href="@path/@loginAccount.get.userName/_edit">Profile</a>
</li> </li>
@@ -13,5 +13,15 @@
<li@if(active=="application"){ class="active"}> <li@if(active=="application"){ class="active"}>
<a href="@path/@loginAccount.get.userName/_application">Applications</a> <a href="@path/@loginAccount.get.userName/_application">Applications</a>
</li> </li>
@gitbucket.core.plugin.PluginRegistry().getAccountSettingMenus.map { menu =>
@menu(context).map { link =>
<li@if(active==link.id){ class="active"}>
<a href="@path/@link.path">@link.label</a>
</li>
}
}
</ul> </ul>
</div> </div>
<div class="main-content">
@body
</div>

View File

@@ -3,7 +3,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main("Create a New Repository"){ @html.main("Create a New Repository"){
<div class="body" style="width: 600px; margin: 10px auto;"> <div class="body main-center">
<h2>Create a new repository</h2> <h2>Create a new repository</h2>
<p class="muted"> <p class="muted">
A repository contains all the files for your project, including the revision history. A repository contains all the files for your project, including the revision history.
@@ -67,7 +67,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
</label> </label>
</fieldset> </fieldset>
<fieldset class="margin form-actions"> <fieldset class="margin form-actions">
<input type="submit" class="btn btn-success btn-lg" value="Create repository"/> <input type="submit" class="btn btn-success" value="Create repository"/>
</fieldset> </fieldset>
</form> </form>
</div> </div>

View File

@@ -15,7 +15,7 @@
<div class="repository-content"> <div class="repository-content">
<div class="block-header"> <div class="block-header">
<a href="@url(repository)">@repository.name</a> <a href="@url(repository)">@repository.name</a>
@if(repository.repository.`private`){ @if(repository.repository.isPrivate){
<i class="octicon octicon-lock"></i> <i class="octicon octicon-lock"></i>
} }
</div> </div>

View File

@@ -4,45 +4,40 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main("SSH Keys"){ @html.main("SSH Keys"){
<div class="container body"> <div class="container body">
<div class="row"> @menu("ssh", settings.ssh){
<div class="col-md-3"> <div class="panel panel-default">
@menu("ssh", settings.ssh) <div class="panel-heading strong">SSH Keys</div>
<div class="panel-body">
@if(sshKeys.isEmpty){
No keys
}
@sshKeys.zipWithIndex.map { case (key, i) =>
@if(i != 0){
<hr>
}
<strong style="line-height: 30px;">@key.title</strong> (@SshUtil.fingerPrint(key.publicKey).getOrElse("Key is invalid."))
<a href="@path/@account.userName/_ssh/delete/@key.sshKeyId" class="btn btn-sm btn-danger pull-right">Delete</a>
}
</div>
</div> </div>
<div class="col-md-9"> <form method="POST" action="@path/@account.userName/_ssh" validate="true">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading strong">SSH Keys</div> <div class="panel-heading strong">Add an SSH Key</div>
<div class="panel-body"> <div class="panel-body">
@if(sshKeys.isEmpty){ <fieldset class="form-group">
No keys <label for="title" class="strong">Title</label>
} <div><span id="error-title" class="error"></span></div>
@sshKeys.zipWithIndex.map { case (key, i) => <input type="text" name="title" id="title" class="form-control"/>
@if(i != 0){ </fieldset>
<hr style="margin-top: 10px;"> <fieldset class="form-group">
} <label for="publicKey" class="strong">Key</label>
<strong>@key.title</strong> (@SshUtil.fingerPrint(key.publicKey).getOrElse("Key is invalid.")) <div><span id="error-publicKey" class="error"></span></div>
<a href="@path/@account.userName/_ssh/delete/@key.sshKeyId" class="btn btn-sm btn-danger pull-right">Delete</a> <textarea name="publicKey" id="publicKey" class="form-control" style="height: 250px;"></textarea>
} </fieldset>
<input type="submit" class="btn btn-success" value="Add"/>
</div> </div>
</div> </div>
<form method="POST" action="@path/@account.userName/_ssh" validate="true"> </form>
<div class="panel panel-default"> }
<div class="panel-heading strong">Add an SSH Key</div>
<div class="panel-body">
<fieldset class="form-group">
<label for="title" class="strong">Title</label>
<div><span id="error-title" class="error"></span></div>
<input type="text" name="title" id="title" class="form-control"/>
</fieldset>
<fieldset class="form-group">
<label for="publicKey" class="strong">Key</label>
<div><span id="error-publicKey" class="error"></span></div>
<textarea name="publicKey" id="publicKey" class="form-control" style="height: 250px;"></textarea>
</fieldset>
<input type="submit" class="btn btn-success" value="Add"/>
</div>
</div>
</form>
</div>
</div>
</div> </div>
} }

View File

@@ -1,25 +1,30 @@
@(active: String)(body: Html)(implicit context: gitbucket.core.controller.Context) @(active: String)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
<div class="container body"> <div class="container body">
<div class="row"> <div class="main-sidebar">
<div class="col-md-3"> <ul class="nav nav-pills nav-stacked" id="system-admin-menu-container">
<ul class="nav nav-tabs nav-stacked side-menu" id="system-admin-menu-container"> <li@if(active=="users"){ class="active"}>
<li@if(active=="users"){ class="active"}> <a href="@path/admin/users">User Management</a>
<a href="@path/admin/users">User Management</a> </li>
</li> <li@if(active=="system"){ class="active"}>
<li@if(active=="system"){ class="active"}> <a href="@path/admin/system">System Settings</a>
<a href="@path/admin/system">System Settings</a> </li>
</li> <li@if(active=="plugins"){ class="active"}>
<li@if(active=="plugins"){ class="active"}> <a href="@path/admin/plugins">Plugins</a>
<a href="@path/admin/plugins">Plugins</a> </li>
</li> <li>
<li> <a href="@path/console/login.jsp">H2 Console</a>
<a href="@path/console/login.jsp">H2 Console</a> </li>
</li> @gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
</ul> @menu(context).map { link =>
</div> <li@if(active==link.id){ class="active"}>
<div class="col-md-9"> <a href="@path/@link.path">@link.label</a>
@body </li>
</div> }
}
</ul>
</div>
<div class="main-content">
@body
</div> </div>
</div> </div>

View File

@@ -13,7 +13,7 @@
<input type="text" name="userName" id="userName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/> <input type="text" name="userName" id="userName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
@if(account.isDefined){ @if(account.isDefined){
<label for="removed"> <label for="removed">
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.removed){checked}/> <input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/>
Disable Disable
</label> </label>
<div> <div>
@@ -53,10 +53,10 @@
<fieldset class="form-group"> <fieldset class="form-group">
<label class="strong">User Type:</label> <label class="strong">User Type:</label>
<label class="radio" for="userType_Normal"> <label class="radio" for="userType_Normal">
<input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.administrator){ checked}/> Normal <input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal
</label> </label>
<label class="radio" for="userType_Admin"> <label class="radio" for="userType_Admin">
<input type="radio" name="isAdmin" id="userType_Admin" value="true"@if(account.isDefined && account.get.administrator){ checked}/> Administrator <input type="radio" name="isAdmin" id="userType_Admin" value="true"@if(account.isDefined && account.get.isAdmin){ checked}/> Administrator
</label> </label>
</fieldset> </fieldset>
<fieldset class="form-group"> <fieldset class="form-group">

View File

@@ -14,7 +14,7 @@
<input type="text" name="groupName" id="groupName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/> <input type="text" name="groupName" id="groupName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
@if(account.isDefined){ @if(account.isDefined){
<label for="removed"> <label for="removed">
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.removed){checked}/> <input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/>
Disable Disable
</label> </label>
} }
@@ -38,7 +38,7 @@
</ul> </ul>
@helper.html.account("memberName", 200) @helper.html.account("memberName", 200)
<input type="button" class="btn btn-default" value="Add" id="addMember"/> <input type="button" class="btn btn-default" value="Add" id="addMember"/>
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.manager).mkString(",")"/> <input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/>
<div> <div>
<span class="error" id="error-members"></span> <span class="error" id="error-members"></span>
</div> </div>
@@ -98,7 +98,7 @@ $(function(){
}); });
@members.map { member => @members.map { member =>
addMemberHTML('@member.userName', @member.manager); addMemberHTML('@member.userName', @member.isManager);
} }
function addMemberHTML(userName, isManager){ function addMemberHTML(userName, isManager){

View File

@@ -14,9 +14,9 @@
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
@users.map { account => @users.map { account =>
<tr> <tr>
<td @if(account.removed){style="background-color: #dddddd;"}> <td @if(account.isRemoved){style="background-color: #dddddd;"}>
<div class="pull-right"> <div class="pull-right">
@if(account.groupAccount){ @if(account.isGroupAccount){
<a href="@path/admin/users/@account.userName/_editgroup">Edit</a> <a href="@path/admin/users/@account.userName/_editgroup">Edit</a>
} else { } else {
<a href="@path/admin/users/@account.userName/_edituser">Edit</a> <a href="@path/admin/users/@account.userName/_edituser">Edit</a>
@@ -25,16 +25,16 @@
<div class="strong"> <div class="strong">
@avatar(account.userName, 20) @avatar(account.userName, 20)
<a href="@url(account.userName)">@account.userName</a> <a href="@url(account.userName)">@account.userName</a>
@if(account.groupAccount){ @if(account.isGroupAccount){
(Group) (Group)
} else { } else {
@if(account.administrator){ @if(account.isAdmin){
(Administrator) (Administrator)
} else { } else {
(Normal) (Normal)
} }
} }
@if(account.groupAccount){ @if(account.isGroupAccount){
@members(account.userName).map { userName => @members(account.userName).map { userName =>
@avatar(userName, 20, tooltip = true) @avatar(userName, 20, tooltip = true)
} }
@@ -42,7 +42,7 @@
</div> </div>
<div> <div>
<hr> <hr>
@if(!account.groupAccount){ @if(!account.isGroupAccount){
<i class="octicon octicon-mail"></i> @account.mailAddress <i class="octicon octicon-mail"></i> @account.mailAddress
} }
@account.url.map { url => @account.url.map { url =>
@@ -52,7 +52,7 @@
<div> <div>
<span class="muted">Registered:</span> @datetime(account.registeredDate) <span class="muted">Registered:</span> @datetime(account.registeredDate)
<span class="muted">Updated:</span> @datetime(account.updatedDate) <span class="muted">Updated:</span> @datetime(account.updatedDate)
@if(!account.groupAccount){ @if(!account.isGroupAccount){
<span class="muted">Last Login:</span> @account.lastLoginDate.map(datetime) <span class="muted">Last Login:</span> @account.lastLoginDate.map(datetime)
} }
</div> </div>

View File

@@ -4,18 +4,8 @@
groups: List[String])(implicit context: gitbucket.core.controller.Context) groups: List[String])(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
<span class="small"> <div id="table-issues-control">
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL"> @helper.html.dropdown("Visibility"){
<i class="octicon octicon-issue-opened @(if(condition.state == "open"){"active"})"></i>
@openCount Open
</a>&nbsp;&nbsp;
<a class="button-link@if(condition.state == "closed"){ selected}" href="@condition.copy(state = "closed").toURL">
<i class="octicon octicon-check @(if(condition.state == "closed"){"active"})"></i>
@closedCount Closed
</a>
</span>
<div class="pull-right" id="table-issues-control">
@helper.html.dropdown("Visibility", flat = true){
<li> <li>
<a href="@(condition.copy(visibility = (if(condition.visibility == Some("private")) None else Some("private"))).toURL)"> <a href="@(condition.copy(visibility = (if(condition.visibility == Some("private")) None else Some("private"))).toURL)">
@helper.html.checkicon(condition.visibility == Some("private")) @helper.html.checkicon(condition.visibility == Some("private"))
@@ -29,7 +19,7 @@
</a> </a>
</li> </li>
} }
@helper.html.dropdown("Organization", flat = true){ @helper.html.dropdown("Organization"){
@groups.map { group => @groups.map { group =>
<li> <li>
<a href="@((if(condition.groups.contains(group)) condition.copy(groups = condition.groups - group) else condition.copy(groups = condition.groups + group)).toURL)"> <a href="@((if(condition.groups.contains(group)) condition.copy(groups = condition.groups - group) else condition.copy(groups = condition.groups + group)).toURL)">
@@ -39,7 +29,7 @@
</li> </li>
} }
} }
@helper.html.dropdown("Sort", flat = true){ @helper.html.dropdown("Sort"){
<li> <li>
<a href="@condition.copy(sort="created", direction="desc").toURL"> <a href="@condition.copy(sort="created", direction="desc").toURL">
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest @helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest

View File

@@ -4,15 +4,17 @@
closedCount: Int, closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition, condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
filter: String, filter: String,
groups: List[String])(implicit context: gitbucket.core.controller.Context) groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main("Issues"){ @html.main("Issues"){
<div class="body"> @sidebar(recentRepositories, userRepositories){
@dashboard.html.tab("issues") @dashboard.html.tab("issues")
<div class="container"> <div class="container">
@issuesnavi(filter, "issues", condition) @issuesnavi(filter, openCount, closedCount, condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups) @issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div> </div>
</div> }
} }

View File

@@ -21,11 +21,6 @@
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) => @issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
<tr> <tr>
<td style="padding-top: 12px; padding-bottom: 12px;"> <td style="padding-top: 12px; padding-bottom: 12px;">
@if(issue.isPullRequest){
<i class="octicon octicon-git-pull-request @(if(issue.closed) "closed" else "open")"></i>
} else {
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")"></i>
}
<a href="@path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65; <a href="@path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65;
@if(issue.isPullRequest){ @if(issue.isPullRequest){
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a> <a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
@@ -50,7 +45,7 @@
</a> </a>
} }
</span> </span>
<div class="small muted" style="margin-left: 20px; margin-top: 2px;"> <div class="small muted" style="margin-top: 2px;">
#@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate) #@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
@milestone.map { milestone => @milestone.map { milestone =>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span> <span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span>

View File

@@ -1,9 +1,17 @@
@(filter: String, @(filter: String,
active: String, openCount: Int,
closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition)(implicit context: gitbucket.core.controller.Context) condition: gitbucket.core.service.IssuesService.IssueSearchCondition)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
<ul class="nav nav-pills pull-left" style="line-height: 14px;"> <ul class="nav nav-pills pull-left" style="line-height: 14px; margin-bottom: 10px;">
<li class="@(if(condition.state == "open"){"active"})">
<a href="@condition.copy(state = "open").toURL">Open <span class="badge">@openCount</span></a>
</li>
<li class="@(if(condition.state == "closed"){"active"})">
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
</li>
@*
<li class="@if(filter == "created_by"){active}"> <li class="@if(filter == "created_by"){active}">
<a href="@path/dashboard/@active/created_by@condition.copy(author = None, assigned = None).toURL">Created</a> <a href="@path/dashboard/@active/created_by@condition.copy(author = None, assigned = None).toURL">Created</a>
</li> </li>
@@ -13,8 +21,5 @@
<li class="@if(filter == "mentioned"){active}"> <li class="@if(filter == "mentioned"){active}">
<a href="@path/dashboard/@active/mentioned@condition.copy(author = None, assigned = None).toURL">Mentioned</a> <a href="@path/dashboard/@active/mentioned@condition.copy(author = None, assigned = None).toURL">Mentioned</a>
</li> </li>
*@
</ul> </ul>
<form method="GET" id="search-filter-form" action="@path/dashboard/@active" class="pull-right">
<input type="text" id="search-filter-box" class="form-control input-lg" name="q" style="width: 400px;"
value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
</form>

View File

@@ -4,15 +4,17 @@
closedCount: Int, closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition, condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
filter: String, filter: String,
groups: List[String])(implicit context: gitbucket.core.controller.Context) groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main("Pull Requests"){ @html.main("Pull Requests"){
<div class="body"> @sidebar(recentRepositories, userRepositories){
@dashboard.html.tab("pulls") @dashboard.html.tab("pulls")
<div class="container"> <div class="container">
@issuesnavi(filter, "pulls", condition) @issuesnavi(filter, openCount, closedCount, condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups) @issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div> </div>
</div> }
} }

View File

@@ -0,0 +1,73 @@
@(recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
<div class="container body">
<div class="dashboard-sidebar">
@if(loginAccount.isEmpty){
<div id="dashboard-signin-form">@html.signinform(settings)</div>
} else {
<div class="panel panel-default">
<div class="panel-heading strong">
Your repositories <span class="badge">@userRepositories.size</span>
</div>
<ul class="list-group list-group-flush">
@if(userRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@userRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
@if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
} else {
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
}
</li>
}
@if(userRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
</li>
}
}
}
</ul>
</div>
}
<div class="panel panel-default">
<div class="panel-heading strong">Recent updated repositories</div>
<ul class="list-group list-group-flush">
@if(recentRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@recentRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</li>
}
@if(recentRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
</li>
}
}
}
</ul>
</div>
</div>
<div class="dashboard-content">
@body
</div>
</div>
<script>
$(function(){
$('#show-more-repos, #show-more-recent-repos').click(function(e){
$(e.target).parents('ul.list-group').find('li.repo-link').show();
$(e.target).parents('li.show-more').remove();
});
});
</script>

View File

@@ -1,52 +1,15 @@
@(active: String = "")(implicit context: gitbucket.core.controller.Context) @(active: String = "")(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
<div class="dashboard-nav"> <ul class="nav nav-tabs" style="margin-bottom: 20px;">
<div class="container"> <li @if(active == ""){ class="active"}><a href="@path/">News Feed</a></li>
<a href="@path/" @if(active == ""){ class="active"}> @if(loginAccount.isDefined){
<i class="octicon octicon-rss"></i> News Feed <li @if(active == "pulls" ){ class="active"}><a href="@path/dashboard/pulls">Pull Requests</a></li>
</a> <li @if(active == "issues"){ class="active"}><a href="@path/dashboard/issues">Issues</a></li>
@if(loginAccount.isDefined){ @gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
<a href="@path/dashboard/pulls" @if(active == "pulls" ){ class="active"}> @tab(context).map { link =>
<i class="octicon octicon-git-pull-request"></i> Pull Requests <li @if(active == link.id){ class="active"}><a href="@path/@link.path">@link.label</a></li>
</a> }
<a href="@path/dashboard/issues" @if(active == "issues"){ class="active"}>
<i class="octicon octicon-issue-opened"></i> Issues
</a>
} }
</div> }
</div> </ul>
<style type="text/css">
div.dashboard-nav {
border-bottom: 1px solid #ddd;
text-align: right;
height: 32px;
margin-bottom: 20px;
}
div.dashboard-nav a {
line-height: 10px;
margin-left: 20px;
padding-bottom: 13px;
padding-left: 4px;
padding-right: 4px;
color: #888;
}
div.dashboard-nav .octicon{
width: 16px;
height: 16px;
font-size: 16px;
color: #888;
}
div.dashboard-nav a:hover,div.dashboard-nav a:hover .octicon {
text-decoration: none;
color: #333;
}
div.dashboard-nav a.active {
border-bottom: 2px solid #bb4444;
color: #333;
}
</style>

View File

@@ -61,8 +61,10 @@
} }
@detailActivity(activity: gitbucket.core.model.Activity, image: String) = { @detailActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div> <div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
<div class="activity-content"> *@
<div>
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div> <div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
<div class="strong"> <div class="strong">
@avatar(activity.activityUserName, 16) @avatar(activity.activityUserName, 16)
@@ -75,8 +77,10 @@
} }
@customActivity(activity: gitbucket.core.model.Activity, image: String)(additionalInfo: Any) = { @customActivity(activity: gitbucket.core.model.Activity, image: String)(additionalInfo: Any) = {
@*
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div> <div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
<div class="activity-content"> *@
<div>
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div> <div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
<div class="strong"> <div class="strong">
@avatar(activity.activityUserName, 16) @avatar(activity.activityUserName, 16)
@@ -87,12 +91,14 @@
} }
@simpleActivity(activity: gitbucket.core.model.Activity, image: String) = { @simpleActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<div class="activity-icon-small"><i class="octicon octicon-@image"></i></div> <div class="activity-icon-small"><i class="octicon octicon-@image"></i></div>
<div class="activity-content"> *@
<div>
<span class="muted small">@helper.html.datetimeago(activity.activityDate)</span>
<div> <div>
@avatar(activity.activityUserName, 16) @avatar(activity.activityUserName, 16)
@activityMessage(activity.message) @activityMessage(activity.message)
<span class="muted small">@helper.html.datetimeago(activity.activityDate)</span>
</div> </div>
</div> </div>
} }

View File

@@ -3,12 +3,12 @@
<div class="input-group" style="margin-bottom: 0px;"> <div class="input-group" style="margin-bottom: 0px;">
@html @html
<span class="input-group-btn"> <span class="input-group-btn">
<span id="@id" class="btn btn-sm btn-default" @if(style.nonEmpty){style="@style"} <span id="@id" class="btn btn-default" @if(style.nonEmpty){style="@style"}
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span> data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
</span> </span>
</div> </div>
} else { } else {
<span id="@id" class="btn btn-sm btn-default" @if(style.nonEmpty){style="@style"} <span id="@id" class="btn btn-default" @if(style.nonEmpty){style="@style"}
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span> data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
} }
<script> <script>

View File

@@ -49,8 +49,8 @@
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){ @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
@if(newCommitId.isDefined){ @if(newCommitId.isDefined){
<div class="pull-right align-right"> <div class="pull-right align-right">
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label> <label class="no-margin"><input type="checkbox" class="ignore-whitespace" value="1"/> Ignore Space</label>
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label> <label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</label>
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a> <a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
</div> </div>
} }
@@ -60,8 +60,8 @@
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){ @if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
@if(newCommitId.isDefined){ @if(newCommitId.isDefined){
<div class="pull-right align-right"> <div class="pull-right align-right">
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label> <label class="no-margin"><input type="checkbox" class="ignore-whitespace" value="1"/> Ignore Space</label>
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label> <label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</label>
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a> <a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
</div> </div>
} }
@@ -77,7 +77,7 @@
@if(diff.changeType == ChangeType.DELETE){ @if(diff.changeType == ChangeType.DELETE){
@if(oldCommitId.isDefined){ @if(oldCommitId.isDefined){
<div class="pull-right align-right"> <div class="pull-right align-right">
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label> <label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</label>
<a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-default btn-sm" title="View the whole file at version @oldCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a> <a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-default btn-sm" title="View the whole file at version @oldCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
</div> </div>
} }

View File

@@ -1,12 +1,10 @@
@(value : String = "", @(value : String = "",
prefix: String = "", prefix: String = "",
style : String = "", style : String = "",
right : Boolean = false, right : Boolean = false)(body: Html)
flat : Boolean = false)(body: Html)
<div class="btn-group" @if(style.nonEmpty){style="@style"}> <div class="btn-group" @if(style.nonEmpty){style="@style"}>
<button <button
@if(flat){style="border: none; background-color: #eee;"} class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">
class="dropdown-toggle @if(!flat){btn btn-default} else {flat} btn-sm" data-toggle="dropdown">
@if(value.isEmpty){ @if(value.isEmpty){
<i class="octicon octicon-gear"></i> <i class="octicon octicon-gear"></i>
} else { } else {

View File

@@ -15,7 +15,7 @@
@import gitbucket.core._ @import gitbucket.core._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
<div class="tabbable"> <div class="tabbable">
<ul class="nav nav-tabs fill-width" style="margin-top: 12px; margin-bottom: 10px;"> <ul class="nav nav-tabs fill-width" style="margin-bottom: 10px;">
<li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li> <li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li>
<li><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li> <li><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li>
</ul> </ul>

View File

@@ -2,7 +2,7 @@
@import gitbucket.core.service.RepositoryService @import gitbucket.core.service.RepositoryService
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@if(repository.repository.`private`){ @if(repository.repository.isPrivate){
<i class="@{if(large){"mega-"}}octicon octicon-lock"></i> <i class="@{if(large){"mega-"}}octicon octicon-lock"></i>
} else { } else {
@if(repository.repository.originUserName.isDefined){ @if(repository.repository.originUserName.isDefined){

View File

@@ -4,91 +4,19 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@main("GitBucket"){ @main("GitBucket"){
<div class="body"> @dashboard.html.sidebar(recentRepositories, userRepositories){
@settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
}
@dashboard.html.tab() @dashboard.html.tab()
<div class="container"> <div class="container">
<div class="row"> <div class="pull-right">
<div class="col-md-8"> <a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
<div class="pull-right">
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
</div>
@helper.html.activities(activities)
</div>
<div class="col-md-4">
@settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
}
@if(loginAccount.isEmpty){
<div id="dashboard-signin-form">@signinform(settings)</div>
} else {
<div class="panel panel-default">
<div class="panel-heading strong">
<div class="pull-right">
<a href="@path/new" class="btn btn-success btn-sm">New repository</a>
</div>
Your repositories <span class="badge">@userRepositories.size</span>
</div>
<ul class="list-group list-group-flush">
@if(userRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@userRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
@if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
} else {
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
}
</li>
}
@if(userRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
</li>
}
}
}
</ul>
</div>
}
<div class="panel panel-default">
<div class="panel-heading strong">Recent updated repositories</div>
<ul class="list-group list-group-flush">
@if(recentRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@recentRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</li>
}
@if(recentRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
</li>
}
}
}
</ul>
</div>
</div>
</div> </div>
@helper.html.activities(activities)
</div> </div>
</div> }
} }
<script>
$(function(){
$('#show-more-repos, #show-more-recent-repos').click(function(e){
$(e.target).parents('ul.list-group').find('li.repo-link').show();
$(e.target).parents('li.show-more').remove();
});
});
</script>

View File

@@ -25,9 +25,9 @@
<div class="text-right"> <div class="text-right">
<input type="hidden" name="issueId" value="@issue.issueId"/> <input type="hidden" name="issueId" value="@issue.issueId"/>
@if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == loginAccount.get.userName)){ @if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == loginAccount.get.userName)){
<input type="submit" class="btn btn-lg btn-default" tabindex="3" formaction="@url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/> <input type="submit" class="btn btn-default" tabindex="3" formaction="@url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
} }
<input type="submit" class="btn btn-lg btn-success" tabindex="2" formaction="@url(repository)/issue_comments/new" value="Comment"/> <input type="submit" class="btn btn-success" tabindex="2" formaction="@url(repository)/issue_comments/new" value="Comment"/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -8,31 +8,26 @@
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("issues", repository){ @html.menu("issues", repository){
<form action="@url(repository)/issues/new" method="POST" validate="true" class="form-group"> <form action="@url(repository)/issues/new" method="POST" validate="true" class="form-group">
<div class="row"> <div class="row-fluid">
<div class="col-md-10"> <div class="col-md-9">
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div> <span id="error-title" class="error"></span>
<div class="panel panel-default issue-box"> <input type="text" id="issue-title" name="title" class="form-control" value="" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
<div class="panel-body"> @helper.html.preview(
<span id="error-title" class="error"></span> repository = repository,
<input type="text" id="issue-title" name="title" class="form-control input-lg" value="" placeholder="Title" autofocus/> content = "",
@helper.html.preview( enableWikiLink = false,
repository = repository, enableRefsLink = true,
content = "", enableLineBreaks = true,
enableWikiLink = false, enableTaskList = true,
enableRefsLink = true, hasWritePermission = hasWritePermission,
enableLineBreaks = true, style = "height: 200px; max-height: 250px;",
enableTaskList = true, elastic = true
hasWritePermission = hasWritePermission, )
style = "height: 200px; max-height: 250px;", <div class="align-right">
elastic = true <input type="submit" class="btn btn-success" value="Submit new issue"/>
)
<div class="align-right">
<input type="submit" class="btn btn-lg btn-success" value="Submit new issue"/>
</div>
</div>
</div> </div>
</div> </div>
<div class="col-md-2"> <div class="col-md-3">
@issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, hasWritePermission, repository) @issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, hasWritePermission, repository)
</div> </div>
</div> </div>

View File

@@ -5,8 +5,8 @@
<textarea id="edit-content-@commentId" class="form-control">@content</textarea> <textarea id="edit-content-@commentId" class="form-control">@content</textarea>
} }
<div> <div>
<input type="button" id="cancel-comment-@commentId" class="btn btn-lg btn-danger" value="Cancel"/> <input type="button" id="cancel-comment-@commentId" class="btn btn-danger" value="Cancel"/>
<input type="button" id="update-comment-@commentId" class="btn btn-lg btn-default pull-right" value="Update comment"/> <input type="button" id="update-comment-@commentId" class="btn btn-default pull-right" value="Update comment"/>
</div> </div>
<script> <script>
$(function(){ $(function(){

View File

@@ -4,8 +4,8 @@
<textarea id="edit-content" class="form-control">@content.getOrElse("")</textarea> <textarea id="edit-content" class="form-control">@content.getOrElse("")</textarea>
} }
<div> <div>
<input type="button" id="cancel-issue" class="btn btn-lg btn-danger" value="Cancel"/> <input type="button" id="cancel-issue" class="btn btn-danger" value="Cancel"/>
<input type="button" id="update-issue" class="btn btn-lg btn-default pull-right" value="Update comment"/> <input type="button" id="update-issue" class="btn btn-default pull-right" value="Update comment"/>
</div> </div>
<script> <script>
$(function(){ $(function(){

View File

@@ -18,7 +18,7 @@
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a> <a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
</div> </div>
<div class="edit-title pull-right" style="display: none;"> <div class="edit-title pull-right" style="display: none;">
<a class="btn btn-default" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a> <a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
</div> </div>
<h1> <h1>
<span class="show-title"> <span class="show-title">
@@ -46,12 +46,12 @@
</span> </span>
</div> </div>
<hr> <hr>
<div class="row" style="margin-top: 15px;"> <div class="row-fluid" style="margin-top: 15px;">
<div class="col-md-10"> <div class="col-md-9">
@commentlist(Some(issue), comments, hasWritePermission, repository) @commentlist(Some(issue), comments, hasWritePermission, repository)
@commentform(issue, true, hasWritePermission, repository) @commentform(issue, true, hasWritePermission, repository)
</div> </div>
<div class="col-md-2"> <div class="col-md-3">
@issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository) @issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div> </div>
</div> </div>

View File

@@ -8,11 +8,11 @@
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
<div style="margin-bottom: 8px;"> <div style="margin-bottom: 14px;">
<span class="muted small strong">Labels</span> <span class="muted small strong">Labels</span>
@if(hasWritePermission){ @if(hasWritePermission){
<div class="pull-right"> <div class="pull-right">
@helper.html.dropdown(right = true) { @helper.html.dropdown("Edit", right = true) {
@labels.map { label => @labels.map { label =>
<li> <li>
<a href="#" class="toggle-label" data-label-id="@label.labelId"> <a href="#" class="toggle-label" data-label-id="@label.labelId">
@@ -33,11 +33,11 @@
@labellist(issueLabels) @labellist(issueLabels)
</ul> </ul>
<hr/> <hr/>
<div style="margin-bottom: 8px;"> <div style="margin-bottom: 14px;">
<span class="muted small strong">Milestone</span> <span class="muted small strong">Milestone</span>
@if(hasWritePermission){ @if(hasWritePermission){
<div class="pull-right"> <div class="pull-right">
@helper.html.dropdown(right = true) { @helper.html.dropdown("Edit", right = true) {
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="octicon octicon-x"></i> Clear this milestone</a></li> <li><a href="javascript:void(0);" class="milestone" data-id=""><i class="octicon octicon-x"></i> Clear this milestone</a></li>
@milestones.filter(_._1.closedDate.isEmpty).map { case (milestone, _, _) => @milestones.filter(_._1.closedDate.isEmpty).map { case (milestone, _, _) =>
<li> <li>
@@ -85,11 +85,11 @@
<input type="hidden" name="milestoneId" value=""/> <input type="hidden" name="milestoneId" value=""/>
} }
<hr/> <hr/>
<div style="margin-bottom: 8px;"> <div style="margin-bottom: 14px;">
<span class="muted small strong">Assignee</span> <span class="muted small strong">Assignee</span>
@if(hasWritePermission){ @if(hasWritePermission){
<div class="pull-right"> <div class="pull-right">
@helper.html.dropdown(right = true) { @helper.html.dropdown("Edit", right = true) {
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li> <li><a href="javascript:void(0);" class="assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
@collaborators.map { collaborator => @collaborators.map { collaborator =>
<li> <li>
@@ -114,7 +114,7 @@
} }
@issue.map { issue => @issue.map { issue =>
<hr/> <hr/>
<div style="margin-bottom: 8px;"> <div style="margin-bottom: 14px;">
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants => @defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
<div class="muted small strong">@participants.size @plural(participants.size, "participant")</div> <div class="muted small strong">@participants.size @plural(participants.size, "participant")</div>
} }

View File

@@ -1,6 +1,6 @@
@(issueLabels: List[gitbucket.core.model.Label]) @(issueLabels: List[gitbucket.core.model.Label])
@if(issueLabels.isEmpty){ @if(issueLabels.isEmpty){
<li><span class="muted small">None yet</span></li> <li><span class="muted">None yet</span></li>
} }
@issueLabels.map { label => @issueLabels.map { label =>
<li><span class="issue-label" style="background-color: #@label.color; color: #@label.fontColor;">@label.labelName</span></li> <li><span class="issue-label" style="background-color: #@label.color; color: #@label.fontColor;">@label.labelName</span></li>

View File

@@ -5,9 +5,12 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"Labels - ${repository.owner}/${repository.name}"){ @html.main(s"Labels - ${repository.owner}/${repository.name}"){
@html.menu("issues", repository){ @html.menu("labels", repository){
@issues.html.navigation("labels", hasWritePermission, repository) @if(loginAccount.isDefined){
<br> <div class="pull-right" style="margin-bottom: 10px;">
<a class="btn btn-success" href="javascript:void(0);" id="new-label-button">New label</a>
</div>
}
<table class="table table-bordered table-hover table-issues" id="new-label-table" style="display: none;"> <table class="table table-bordered table-hover table-issues" id="new-label-table" style="display: none;">
<tr><td></td></tr> <tr><td></td></tr>
</table> </table>

View File

@@ -13,7 +13,24 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){ @html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu(target, repository){ @html.menu(target, repository){
@navigation(target, true, repository, Some(condition)) <ul class="nav nav-pills pull-left" style="line-height: 14px; margin-bottom: 10px;">
<li class="@if(condition.state == "open"){active}">
<a href="@condition.copy(state = "open").toURL">Open <span class="badge">@openCount</span></a>
</li>
<li class="@if(condition.state == "closed"){active}">
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
</li>
</ul>
<form method="GET" id="search-filter-form" class="form-inline pull-right">
@if(loginAccount.isDefined){
@if(target == "issues"){
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
}
@if(target == "pulls"){
<a class="btn btn-success" href="@url(repository)/compare">New pull request</a>
}
}
</form>
@listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission) @listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), hasWritePermission)
@if(hasWritePermission){ @if(hasWritePermission){
<form id="batcheditForm" method="POST"> <form id="batcheditForm" method="POST">

View File

@@ -12,6 +12,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import gitbucket.core.service.IssuesService.IssueInfo @import gitbucket.core.service.IssuesService.IssueInfo
@*
@if(condition.nonEmpty){ @if(condition.nonEmpty){
<div style="width: 100%; display: inline-block;"> <div style="width: 100%; display: inline-block;">
<a href="@gitbucket.core.service.IssuesService.IssueSearchCondition().toURL" class="header-link"> <a href="@gitbucket.core.service.IssuesService.IssueSearchCondition().toURL" class="header-link">
@@ -20,23 +21,14 @@
</a> </a>
</div> </div>
} }
*@
<table class="table table-bordered table-hover table-issues"> <table class="table table-bordered table-hover table-issues">
<thead> <thead>
<tr> <tr>
<th style="background-color: #eee;"> <th style="background-color: #eee;">
<input type="checkbox"/> <input type="checkbox"/>
<span class="small"> <span id="table-issues-control">
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL"> @helper.html.dropdown("Author") {
<i class="octicon octicon-issue-opened @(if(condition.state == "open"){"active"})"></i>
@openCount Open
</a>&nbsp;&nbsp;
<a class="button-link@if(condition.state == "closed"){ selected}" href="@condition.copy(state = "closed").toURL">
<i class="octicon octicon-check @(if(condition.state == "closed"){"active"})"></i>
@closedCount Closed
</a>
</span>
<div class="pull-right" id="table-issues-control">
@helper.html.dropdown("Author", flat = true) {
@collaborators.map { collaborator => @collaborators.map { collaborator =>
<li> <li>
<a href="@condition.copy(author = (if(condition.author == Some(collaborator)) None else Some(collaborator))).toURL"> <a href="@condition.copy(author = (if(condition.author == Some(collaborator)) None else Some(collaborator))).toURL">
@@ -46,7 +38,7 @@
</li> </li>
} }
} }
@helper.html.dropdown("Label", flat = true) { @helper.html.dropdown("Label") {
@labels.map { label => @labels.map { label =>
<li> <li>
<a href="@condition.copy(labels = (if(condition.labels.contains(label.labelName)) condition.labels - label.labelName else condition.labels + label.labelName)).toURL"> <a href="@condition.copy(labels = (if(condition.labels.contains(label.labelName)) condition.labels - label.labelName else condition.labels + label.labelName)).toURL">
@@ -57,7 +49,7 @@
</li> </li>
} }
} }
@helper.html.dropdown("Milestone", flat = true) { @helper.html.dropdown("Milestone") {
<li> <li>
<a href="@condition.copy(milestone = Some(None)).toURL"> <a href="@condition.copy(milestone = Some(None)).toURL">
@helper.html.checkicon(condition.milestone == Some(None)) Issues with no milestone @helper.html.checkicon(condition.milestone == Some(None)) Issues with no milestone
@@ -71,7 +63,7 @@
</li> </li>
} }
} }
@helper.html.dropdown("Assignee", flat = true) { @helper.html.dropdown("Assignee") {
@collaborators.map { collaborator => @collaborators.map { collaborator =>
<li> <li>
<a href="@condition.copy(assigned = Some(collaborator)).toURL"> <a href="@condition.copy(assigned = Some(collaborator)).toURL">
@@ -81,7 +73,7 @@
</li> </li>
} }
} }
@helper.html.dropdown("Sort", flat = true){ @helper.html.dropdown("Sort"){
<li> <li>
<a href="@condition.copy(sort="created", direction="desc").toURL"> <a href="@condition.copy(sort="created", direction="desc").toURL">
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest @helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
@@ -113,14 +105,14 @@
</a> </a>
</li> </li>
} }
</div> </span>
@if(hasWritePermission){ @if(hasWritePermission){
<div class="pull-right" id="table-issues-batchedit"> <span id="table-issues-batchedit">
@helper.html.dropdown("Mark as", flat = true) { @helper.html.dropdown("Mark as") {
<li><a href="javascript:void(0);" class="toggle-state" data-id="open">Open</a></li> <li><a href="javascript:void(0);" class="toggle-state" data-id="open">Open</a></li>
<li><a href="javascript:void(0);" class="toggle-state" data-id="close">Close</a></li> <li><a href="javascript:void(0);" class="toggle-state" data-id="close">Close</a></li>
} }
@helper.html.dropdown("Label", flat = true) { @helper.html.dropdown("Label") {
@labels.map { label => @labels.map { label =>
<li> <li>
<a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId"> <a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId">
@@ -131,19 +123,19 @@
</li> </li>
} }
} }
@helper.html.dropdown("Milestone", flat = true) { @helper.html.dropdown("Milestone") {
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="">No milestone</a></li> <li><a href="javascript:void(0);" class="toggle-milestone" data-id="">No milestone</a></li>
@milestones.filter(_.closedDate.isEmpty).map { milestone => @milestones.filter(_.closedDate.isEmpty).map { milestone =>
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="@milestone.milestoneId">@milestone.title</a></li> <li><a href="javascript:void(0);" class="toggle-milestone" data-id="@milestone.milestoneId">@milestone.title</a></li>
} }
} }
@helper.html.dropdown("Assignee", flat = true) { @helper.html.dropdown("Assignee") {
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li> <li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
@collaborators.map { collaborator => @collaborators.map { collaborator =>
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="octicon"></i>@avatar(collaborator, 20) @collaborator</a></li> <li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="octicon"></i>@avatar(collaborator, 20) @collaborator</a></li>
} }
} }
</div> </span>
} }
</th> </th>
</tr> </tr>
@@ -157,9 +149,11 @@
} else { } else {
No pull requests to show. No pull requests to show.
} }
@*
@if(condition.labels.nonEmpty || condition.milestone.isDefined){ @if(condition.labels.nonEmpty || condition.milestone.isDefined){
<a href="@condition.copy(labels = Set.empty, milestone = None).toURL">Clear active filters.</a> <a href="@condition.copy(labels = Set.empty, milestone = None).toURL">Clear active filters.</a>
} else { } else {
*@
@if(repository.isDefined){ @if(repository.isDefined){
@if(target == "issues"){ @if(target == "issues"){
<a href="@url(repository.get)/issues/new">Create a new issue.</a> <a href="@url(repository.get)/issues/new">Create a new issue.</a>
@@ -167,7 +161,9 @@
<a href="@url(repository.get)/compare">Create a new pull request.</a> <a href="@url(repository.get)/compare">Create a new pull request.</a>
} }
} }
@*
} }
*@
</td> </td>
</tr> </tr>
} }
@@ -177,7 +173,9 @@
@if(hasWritePermission){ @if(hasWritePermission){
<input type="checkbox" value="@issue.issueId"/> <input type="checkbox" value="@issue.issueId"/>
} }
@*
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")" style="margin-right: 3px;"></i> <i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")" style="margin-right: 3px;"></i>
*@
@if(repository.isEmpty){ @if(repository.isEmpty){
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a>&nbsp;&#xFF65; <a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a>&nbsp;&#xFF65;
} }
@@ -204,7 +202,7 @@
</a> </a>
} }
</span> </span>
<div class="small muted" style="margin-left: 40px; margin-top: 2px;"> <div class="small muted" style="margin-left: 12px; margin-top: 2px;">
#@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username") #@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username")
@milestone.map { milestone => @milestone.map { milestone =>
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span> <span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span>

View File

@@ -3,13 +3,10 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){ @html.main(s"Milestones - ${repository.owner}/${repository.name}"){
@html.menu("issues", repository){ @html.menu("milestones", repository){
@if(milestone.isEmpty){ @if(milestone.isEmpty){
<h4>New milestone</h4> <h4>New milestone</h4>
<div class="muted">Create a new milestone to help organize your issues and pull requests.</div> <div class="muted">Create a new milestone to help organize your issues and pull requests.</div>
} else {
@issues.html.navigation("milestones", false, repository)
<br><br>
} }
<hr style="margin-top: 12px; margin-bottom: 18px;" class="fill-width"/> <hr style="margin-top: 12px; margin-bottom: 18px;" class="fill-width"/>
<form method="POST" action="@url(repository)/issues/milestones/@if(milestone.isEmpty){new}else{@milestone.get.milestoneId/edit}" validate="true"> <form method="POST" action="@url(repository)/issues/milestones/@if(milestone.isEmpty){new}else{@milestone.get.milestoneId/edit}" validate="true">
@@ -30,7 +27,7 @@
<hr> <hr>
<div class="pull-right"> <div class="pull-right">
@if(milestone.isEmpty){ @if(milestone.isEmpty){
<input type="submit" class="btn btn-default" value="Create milestone"/> <input type="submit" class="btn btn-success" value="Create milestone"/>
} else { } else {
@if(milestone.get.closedDate.isDefined){ @if(milestone.get.closedDate.isDefined){
<input type="button" class="btn btn-default" value="Open" id="open" <input type="button" class="btn btn-default" value="Open" id="open"
@@ -39,7 +36,7 @@
<input type="button" class="btn btn-default" value="Close" id="close" <input type="button" class="btn btn-default" value="Close" id="close"
onclick="location.href='@url(repository)/issues/milestones/@milestone.get.milestoneId/open';"/> onclick="location.href='@url(repository)/issues/milestones/@milestone.get.milestoneId/open';"/>
} }
<input type="submit" class="btn btn-default" value="Update milestone"/> <input type="submit" class="btn btn-success" value="Update milestone"/>
} }
</div> </div>
</form> </form>

View File

@@ -5,20 +5,21 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){ @html.main(s"Milestones - ${repository.owner}/${repository.name}"){
@html.menu("issues", repository){ @html.menu("milestones", repository){
@issues.html.navigation("milestones", hasWritePermission, repository) @if(loginAccount.isDefined){
<br> <div class="pull-right" style="margin-bottom: 10px;">
<a class="btn btn-success" href="@url(repository)/issues/milestones/new">New milestone</a>
</div>
}
<table class="table table-bordered table-hover table-issues"> <table class="table table-bordered table-hover table-issues">
<thead> <thead>
<tr> <tr>
<th style="background-color: #eee;"> <th style="background-color: #eee;">
<span class="small"> <span class="small">
<a class="button-link@if(state == "open"){ selected}" href="?state=open"> <a class="button-link@if(state == "open"){ selected}" href="?state=open">
<i class="octicon octicon-milestone @(if(state == "open"){"active"})"></i>
@milestones.count(_._1.closedDate.isEmpty) Open @milestones.count(_._1.closedDate.isEmpty) Open
</a>&nbsp;&nbsp; </a>&nbsp;&nbsp;
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed"> <a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
<i class="octicon octicon-milestone @(if(state == "closed"){"active"})"></i>
@milestones.count(_._1.closedDate.isDefined) Closed @milestones.count(_._1.closedDate.isDefined) Closed
</a> </a>
</span> </span>

View File

@@ -1,56 +0,0 @@
@(active: String,
newButton: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
condition: Option[gitbucket.core.service.IssuesService.IssueSearchCondition] = None)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
<ul class="nav nav-pills pull-left" style="line-height: 14px;">
<li class="@if(active == "issues" ){active}"><a href="@url(repository)/issues">Issues</a></li>
<li class="@if(active == "pulls" ){active}"><a href="@url(repository)/pulls">Pull requests</a></li>
<li class="@if(active == "labels" ){active}"><a href="@url(repository)/issues/labels">Labels</a></li>
<li class="@if(active == "milestones"){active}"><a href="@url(repository)/issues/milestones">Milestones</a></li>
</ul>
<form method="GET" id="search-filter-form" style="margin-bottom: 0px;" class="form-inline pull-right">
@condition.map { condition =>
@if(loginAccount.isDefined){
<div class="form-group" style="width: 300px">
<div class="input-group" style="margin-bottom: 0px;">
<div class="input-group-btn">
<button type="button" class="btn btn-default btn-lg dropdown-toggle" data-toggle="dropdown">
Filter
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="?q=is:open">Open issues and pull requests</a></li>
<li><a href="?q=is:open+is:issue+author:@urlEncode(loginAccount.get.userName)">Your issues</a></li>
<li><a href="?q=is:open+is:pr+author:@urlEncode(loginAccount.get.userName)">Your pull requests</a></li>
<li><a href="?q=is:open+assignee:@urlEncode(loginAccount.get.userName)">Everything assigned to you</a></li>
@*
<li><a href="?q=is:open+mentions:@urlEncode(loginAccount.get.userName)">Everything mentioning you</a></li>
*@
</ul>
</div>
<input type="text" id="search-filter-box" class="form-control input-lg" size="40" name="q" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
</div>
</div>
} else {
<input type="text" id="search-filter-box" class="form-control input-lg" size="40" name="q" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
}
}
@if(loginAccount.isDefined){
@if(newButton){
@if(active == "issues"){
<a class="btn btn-success btn-lg" href="@url(repository)/issues/new">New issue</a>
}
@if(active == "pulls"){
<a class="btn btn-success btn-lg" href="@url(repository)/compare">New pull request</a>
}
@if(active == "labels"){
<a class="btn btn-success btn-lg" href="javascript:void(0);" id="new-label-button">New label</a>
}
@if(active == "milestones"){
<a class="btn btn-success btn-lg" href="@url(repository)/issues/milestones/new">New milestone</a>
}
}
}
</form>

View File

@@ -11,10 +11,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="@assets/common/images/gitbucket.png" type="image/vnd.microsoft.icon" /> <link rel="icon" href="@assets/common/images/gitbucket.png" type="image/vnd.microsoft.icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="@assets/vendors/bootstrap-theme-github-3.1.1/css/bootstrap.css" rel="stylesheet"> <link href="@assets/vendors/bootstrap-3.3.6/css/bootstrap.min.css" rel="stylesheet">
@*
<link href="@assets/vendors/bootstrap-theme-github-3.1.1/css/bootstrap-responsive.css" rel="stylesheet">
*@
<link href="@assets/vendors/octicons/octicons.css" rel="stylesheet"> <link href="@assets/vendors/octicons/octicons.css" rel="stylesheet">
<link href="@assets/vendors/datepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet"> <link href="@assets/vendors/datepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
<link href="@assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet"> <link href="@assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
@@ -25,7 +22,7 @@
<script src="@assets/vendors/dropzone/dropzone.js"></script> <script src="@assets/vendors/dropzone/dropzone.js"></script>
<script src="@assets/common/js/validation.js"></script> <script src="@assets/common/js/validation.js"></script>
<script src="@assets/common/js/gitbucket.js"></script> <script src="@assets/common/js/gitbucket.js"></script>
<script src="@assets/vendors/bootstrap-theme-github-3.1.1/js/bootstrap.js"></script> <script src="@assets/vendors/bootstrap-3.3.6/js/bootstrap.js"></script>
<script src="@assets/vendors/bootstrap3-typeahead/bootstrap3-typeahead.js"></script> <script src="@assets/vendors/bootstrap3-typeahead/bootstrap3-typeahead.js"></script>
<script src="@assets/vendors/datepicker/js/moment.js"></script> <script src="@assets/vendors/datepicker/js/moment.js"></script>
<script src="@assets/vendors/datepicker/js/bootstrap-datetimepicker.min.js"></script> <script src="@assets/vendors/datepicker/js/bootstrap-datetimepicker.min.js"></script>
@@ -36,7 +33,7 @@
<script src="@assets/vendors/facebox/facebox.js"></script> <script src="@assets/vendors/facebox/facebox.js"></script>
<script src="@assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script> <script src="@assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script>
@repository.map { repository => @repository.map { repository =>
@if(!repository.repository.`private`){ @if(!repository.repository.isPrivate){
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" /> <meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
} }
} }
@@ -44,7 +41,7 @@
<body> <body>
<form id="search" action="@path/search" method="POST" class="form-inline"> <form id="search" action="@path/search" method="POST" class="form-inline">
<nav class="navbar navbar-default"> <nav class="navbar navbar-default">
<div class="container" style="width: 980px;"> <div class="container">
@* TODO: for plugi-ins? @* TODO: for plugi-ins?
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span> <span class="icon-bar"></span>
@@ -53,14 +50,14 @@
</button> </button>
*@ *@
<a class="navbar-brand" href="@path/"> <a class="navbar-brand" href="@path/">
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket <img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px; display: inline;"/>GitBucket
@defining(AutoUpdate.getCurrentVersion){ version => @defining(AutoUpdate.getCurrentVersion){ version =>
<span class="header-version">@version.majorVersion.@version.minorVersion</span> <span class="header-version">@version.majorVersion.@version.minorVersion</span>
} }
</a> </a>
@if(loginAccount.isDefined){ @if(loginAccount.isDefined){
@repository.map { repository => @repository.map { repository =>
<input type="text" name="query" class="form-control" style="width: 200px; margin-bottom: 0px;" placeholder="Search this repository"/> <input type="text" name="query" class="form-control" style="width: 400px; margin-top: 3px; margin-bottom: 3px;" placeholder="Search this repository"/>
<input type="hidden" name="owner" value="@repository.owner"/> <input type="hidden" name="owner" value="@repository.owner"/>
<input type="hidden" name="repository" value="@repository.name"/> <input type="hidden" name="repository" value="@repository.name"/>
} }
@@ -69,11 +66,16 @@
} else { } else {
@* TODO: merge with below *@ @* TODO: merge with below *@
@repository.map { repository => @repository.map { repository =>
<input type="text" name="query" class="form-control" style="width: 200px; margin-top: 6px; margin-bottom: 0px;" placeholder="Search this repository"/> <input type="text" name="query" class="form-control" style="width: 400px; margin-top: 3px; margin-bottom: 3px;" placeholder="Search this repository"/>
<input type="hidden" name="owner" value="@repository.owner"/> <input type="hidden" name="owner" value="@repository.owner"/>
<input type="hidden" name="repository" value="@repository.name"/> <input type="hidden" name="repository" value="@repository.name"/>
} }
} }
@gitbucket.core.plugin.PluginRegistry().getGlobalMenus.map { menu =>
@menu(context).map { link =>
<a href="@path/@link.path" class="global-header-menu">@link.label</a>
}
}
@if(loginAccount.isDefined){ @if(loginAccount.isDefined){
<div class="pull-right" style="margin-top: 6px;"> <div class="pull-right" style="margin-top: 6px;">
<div class="btn-group" style="margin-right: 8px;"> <div class="btn-group" style="margin-right: 8px;">
@@ -92,7 +94,7 @@
<ul class="dropdown-menu pull-right"> <ul class="dropdown-menu pull-right">
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li> <li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
<li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li> <li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li>
@if(loginAccount.get.administrator){ @if(loginAccount.get.isAdmin){
<li><a href="@path/admin/users">System administration</a></li> <li><a href="@path/admin/users">System administration</a></li>
} }
<li><a href="@path/signout">Sign out</a></li> <li><a href="@path/signout">Sign out</a></li>
@@ -100,7 +102,7 @@
</div> </div>
</div> </div>
} else { } else {
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn btn-default pull-right" style="margin-top: 6px;" id="signin">Sign in</a> <a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn btn-default pull-right" style="margin-top: 3px; margin-bottom: 3px;" id="signin">Sign in</a>
} }
</div> </div>
</nav> </nav>

View File

@@ -1,20 +1,21 @@
@(active: String, @(active: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
id: Option[String] = None, id: Option[String] = None,
isRepoTop: Boolean = false,
isNoGroup: Boolean = true,
info: Option[Any] = None, info: Option[Any] = None,
error: Option[Any] = None)(body: Html)(implicit context: gitbucket.core.controller.Context) error: Option[Any] = None)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@menuitem(path: String, name: String, icon: String, label: String, count: Int = 0) = { @menuitem(path: String, name: String, label: String, icon: String, count: Int = 0) = {
<li @if(active == name){class="active"}> <li @if(active == name){class="active"}>
<a href="@url(repository)@path"> <a href="@url(repository)@path">
<i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i> <span class="pc">@label</span> <i class="menu-icon octicon octicon-@icon"></i>
@if(count > 0){ <span class="pc">
<span class="badge">@count</span> @label
} @if(count > 0){
<span class="badge">@count</span>
}
</span>
</a> </a>
</li> </li>
} }
@@ -24,29 +25,6 @@
@helper.html.information(info) @helper.html.information(info)
@helper.html.error(error) @helper.html.error(error)
<div class="head"> <div class="head">
@if(repository.commitCount > 0){
<div class="input-group pull-right">
<span class="fork">
<span class="input-group-btn">
@if(loginAccount.isEmpty){
<a title="You must be signed in to fork a repository" href="@path/signin?redirect=@urlEncode(s"${path}/${repository.owner}/${repository.name}")" class="btn btn-default">Fork</a>
} else {
@if(isNoGroup) {
<a id="fork-link" href="javascript:void(0);" class="btn btn-default">Fork</a>
} else {
<a href="@path/@repository.owner/@repository.name/fork" class="btn btn-default" rel="facebox">Fork</a>
}
}
</span>
<span class="count"><a href="@url(repository)/network/members">@repository.forkedCount</a></span>
</span>
</div>
@if(loginAccount.isDefined && isNoGroup){
<form id="fork-form" method="post" action="@path/@repository.owner/@repository.name/fork" style="display: none;">
<input type="hidden" name="account" value="@loginAccount.get.userName"/>
</form>
}
}
@helper.html.repositoryicon(repository, true) @helper.html.repositoryicon(repository, true)
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a> <a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a>
@@ -56,92 +34,39 @@
forked from <a href="@path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a> forked from <a href="@path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
</div> </div>
} }
@x.description.map { description =>
<div class="normal muted" style="margin-left: 36px; font-size: 80%;">@detectAndRenderLinks(description)</div>
}
} }
</div> </div>
<ul class="headmenu">
@menuitem("" , "code" , "code" , "Code")
@menuitem("/issues", "issues" , "issue-opened" , "Issues", repository.issueCount)
@menuitem("/pulls" , "pulls" , "git-pull-request" , "Pull Requests", repository.pullCount)
@menuitem("/wiki" , "wiki" , "book" , "Wiki")
@if(loginAccount.isDefined && (loginAccount.get.administrator || repository.managers.contains(loginAccount.get.userName))){
@menuitem("/settings" , "settings" , "tools", "Settings")
}
</ul>
</div> </div>
</div> </div>
<div class="container body"> <div class="container body">
@if(isRepoTop){ <div class="main-sidebar">
@repository.repository.description.map { description => <ul class="nav nav-pills nav-stacked">
<p class="description">@detectAndRenderLinks(description)</p> @menuitem("" ,"files" ,"Files", "code")
} @if(repository.commitCount != 0) {
<div style="margin-bottom: 10px; padding: 4px;" class="panel panel-default"> @menuitem("/branches" ,"branches" ,"Branches", "git-branch", repository.branchList.length)
<table class="fill-width"> @menuitem("/tags" ,"tags" ,"Tags", "tag", repository.tags.length)
<tr> }
<td style="width: 33%; text-align: center;"> @menuitem("/issues" ,"issues" ,"Issues", "issue-opened", repository.issueCount)
<a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link"> @menuitem("/pulls" ,"pulls" ,"Pull Requests", "git-pull-request", repository.pullCount)
<i class="octicon octicon-history"></i> @menuitem("/issues/labels" ,"labels" ,"Labels", "tag")
@if(repository.commitCount > 10000){ @menuitem("/issues/milestones" ,"milestones" ,"Milestones", "milestone")
<strong>10000+</strong> commits @menuitem("/wiki" ,"wiki" ,"Wiki", "book")
} else { @menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
<strong>@repository.commitCount</strong> commits @if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
} @menuitem("/settings" , "settings" , "Settings", "tools")
</a> }
</td> @gitbucket.core.plugin.PluginRegistry().getRepositoryMenus.map { menu =>
<td style="width: 33%; text-align: center;"> @menu(repository, context).map { link =>
<a href="@url(repository)/branches" class="header-link" class="header-link"> @menuitem(link.path, link.id, link.label, link.icon.getOrElse("ruby"))
<i class="octicon octicon-git-branch"></i> }
<strong>@repository.branchList.length</strong> branches }
</a> </ul>
</td> </div>
<td style="width: 33%; text-align: center;"> <div class="main-content">
<a href="@url(repository)/tags" class="header-link" class="header-link"> @body
<i class="octicon octicon-tag"></i> </div>
<strong>@repository.tags.length</strong> releases
</a>
</td>
</tr>
</table>
</div>
}
@body
</div> </div>
<script>
$(function(){
$('ul.sidemenu a').mouseover(function(e){
var target = e.target;
if(e.target.tagName == "I"){
target = e.target.parentElement;
}
$(target).prev('div.gradient').css('border-left', '1px solid silver');
});
$('ul.sidemenu a').mouseout(function(e){
var target = e.target;
if(e.target.tagName == "I"){
target = e.target.parentElement;
}
$(target).prev('div.gradient').css('border-left', '1px solid #eee');
});
$('a[rel*=facebox]').facebox({
'loadingImage': '@assets/vendors/facebox/loading.gif',
'closeImage': '@assets/vendors/facebox/closelabel.png'
});
$(document).on("click", ".js-fork-owner-select-target", function() {
if (!$(this).hasClass("disabled")) {
var account = $(this).text().replace("@@", "");
$("#account").val(account);
$("#fork").submit();
}
});
@if(loginAccount.isDefined){
$(document).on("click", "a#fork-link", function(e) {
e.preventDefault();
$('#fork-form').submit();
});
}
});
</script>

View File

@@ -4,42 +4,41 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import gitbucket.core.model._ @import gitbucket.core.model._
<div class="commit-list"> <table class="table table-bordered">
@commits.map { day => @commits.map { day =>
<div class="muted" style="background-color: white;"> <tr>
<i class="octicon octicon-git-commit"></i> Commits on @date(day.head.commitTime) <th rowspan="@day.size" width="100">@date(day.head.commitTime)</th>
</div> @day.zipWithIndex.map { case (commit, i) =>
<div class="list-group box-commits"> @if(i != 0){ <tr> }
@day.map { commit => <td>
<div class="list-group-item"> <div class="pull-right text-right">
<ul class="nav nav-pills pull-right"> <a href="@url(repository)/commit/@commit.id" class="monospace commit-message strong"><i class="octicon octicon-diff" style="color: black;"></i>@commit.id.substring(0, 7)</a><br>
<li><a href="@url(repository)/commit/@commit.id" class="link monospace" style="line-height: 16px;">@commit.id.substring(0, 7)</a></li> <a href="@url(repository)/tree/@commit.id" class="button-link">Browse files »</a>
<li><a href="@url(repository)/tree/@commit.id" style="line-height: 16px;"><i class="octicon octicon-code link"></i></a></li> </div>
</ul> <div>
<div class="commit-avatar-image">@avatarLink(commit, 40)</div>
<div> <div>
<div class="commit-avatar-image">@avatarLink(commit, 40)</div> <a href="@url(repository)/commit/@commit.id" class="commit-message strong">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
}
<br>
@if(commit.description.isDefined){
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
}
<div> <div>
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a> @user(commit.authorName, commit.authorEmailAddress, "username")
@if(commit.description.isDefined){ <span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a> @if(commit.isDifferentFromAuthor) {
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
} }
<br>
@if(commit.description.isDefined){
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
}
<div style="margin-top: 2px;">
@user(commit.authorName, commit.authorEmailAddress, "username")
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
@if(commit.isDifferentFromAuthor) {
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
}
</div>
</div> </div>
</div> </div>
</div> </div>
} </td>
</div> </tr>
} }
</div> }
</table>

View File

@@ -54,36 +54,31 @@
<div id="pull-request-form" @*class="box"*@ style="display: none; margin-bottom: 20px;"> <div id="pull-request-form" @*class="box"*@ style="display: none; margin-bottom: 20px;">
<form method="POST" action="@path/@originRepository.owner/@originRepository.name/pulls/new" validate="true"> <form method="POST" action="@path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-9">
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div> <span class="error" id="error-title"></span>
<div class="panel panel-default issue-box"> <input type="text" name="title" class="form-control" style="margin-bottom: 6px;" placeholder="Title"/>
<div class="panel-body"> @helper.html.preview(
<span class="error" id="error-title"></span> repository = repository,
<input type="text" name="title" class="form-control input-lg" placeholder="Title"/> content = "",
@helper.html.preview( enableWikiLink = false,
repository = repository, enableRefsLink = true,
content = "", enableLineBreaks = true,
enableWikiLink = false, enableTaskList = true,
enableRefsLink = true, hasWritePermission = true,
enableLineBreaks = true, style = "height: 200px;"
enableTaskList = true, )
hasWritePermission = true, <input type="hidden" name="targetUserName" value="@originRepository.owner"/>
style = "height: 200px;" <input type="hidden" name="targetBranch" value="@originId"/>
) <input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
<input type="hidden" name="targetUserName" value="@originRepository.owner"/> <input type="hidden" name="requestRepositoryName" value="@forkedRepository.name"/>
<input type="hidden" name="targetBranch" value="@originId"/> <input type="hidden" name="requestBranch" value="@forkedId"/>
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/> <input type="hidden" name="commitIdFrom" value="@sourceId"/>
<input type="hidden" name="requestRepositoryName" value="@forkedRepository.name"/> <input type="hidden" name="commitIdTo" value="@commitId"/>
<input type="hidden" name="requestBranch" value="@forkedId"/> <div class="align-right">
<input type="hidden" name="commitIdFrom" value="@sourceId"/> <input type="submit" class="btn btn-success" value="Create pull request"/>
<input type="hidden" name="commitIdTo" value="@commitId"/>
<div class="align-right">
<input type="submit" class="btn btn-lg btn-success" value="Create pull request"/>
</div>
</div>
</div> </div>
</div> </div>
<div class="col-md-2"> <div class="col-md-3">
@issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map((_, 0, 0)), labels, hasOriginWritePermission, repository) @issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map((_, 0, 0)), labels, hasOriginWritePermission, repository)
</div> </div>
</div> </div>

View File

@@ -12,7 +12,7 @@
@import gitbucket.core.model._ @import gitbucket.core.model._
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-9">
<div id="comment-list"> <div id="comment-list">
@issues.html.commentlist(Some(issue), comments, hasWritePermission, repository, Some(pullreq)) @issues.html.commentlist(Some(issue), comments, hasWritePermission, repository, Some(pullreq))
</div> </div>
@@ -44,7 +44,7 @@
@issues.html.commentform(issue, !merged, hasWritePermission, repository) @issues.html.commentform(issue, !merged, hasWritePermission, repository)
} }
</div> </div>
<div class="col-md-2"> <div class="col-md-3">
@issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository) @issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div> </div>
</div> </div>

View File

@@ -51,7 +51,7 @@
<div class="pull-right"> <div class="pull-right">
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/update_branch"> <form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/update_branch">
<input type="hidden" name="expected_head_oid" value="@pullreq.commitIdFrom"> <input type="hidden" name="expected_head_oid" value="@pullreq.commitIdFrom">
<button class="btn"@if(!status.canUpdate){ disabled="true"} id="update-branch-button">Update branch</button> <button class="btn btn-default"@if(!status.canUpdate){ disabled="true"} id="update-branch-button">Update branch</button>
</form> </form>
</div> </div>
} }
@@ -83,7 +83,7 @@
</div> </div>
@if(status.hasMergePermission){ @if(status.hasMergePermission){
<div style="padding:15px;border-top:solid 1px #e5e5e5;background:#fafafa"> <div style="padding:15px;border-top:solid 1px #e5e5e5;background:#fafafa">
<input type="button" class="btn btn-lg @if(!status.hasProblem){btn-success} else {btn-default}" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/> <input type="button" class="btn @if(!status.hasProblem){btn-success} else {btn-default}" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/>
&nbsp;&nbsp;You can also merge branches on the <a href="#" class="show-command-line">command line</a>. &nbsp;&nbsp;You can also merge branches on the <a href="#" class="show-command-line">command line</a>.
<div id="command-line" style="display: none;margin-top: 15px;"> <div id="command-line" style="display: none;margin-top: 15px;">
<hr /> <hr />

View File

@@ -26,7 +26,7 @@
} }
</div> </div>
<div class="edit-title pull-right" style="display: none;"> <div class="edit-title pull-right" style="display: none;">
<a class="btn" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a> <a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
</div> </div>
<h1> <h1>
<span class="show-title"> <span class="show-title">
@@ -35,7 +35,7 @@
</span> </span>
<span class="edit-title" style="display: none;"> <span class="edit-title" style="display: none;">
<span id="error-edit-title" class="error"></span> <span id="error-edit-title" class="error"></span>
<input type="text" style="width: 700px;" id="edit-title" value="@issue.title"/> <input type="text" class="form-control" style="width: 700px;" id="edit-title" value="@issue.title"/>
</span> </span>
</h1> </h1>
</div> </div>
@@ -66,7 +66,7 @@
</span> </span>
} }
</div> </div>
<ul class="nav nav-tabs fill-width pull-left" id="pullreq-tab"> <ul class="nav nav-tabs fill-width" id="pullreq-tab">
<li><a href="#conversation">Conversation <span class="badge">@comments.flatMap @{ <li><a href="#conversation">Conversation <span class="badge">@comments.flatMap @{
case comment: IssueComment => Some(comment) case comment: IssueComment => Some(comment)
case _: CommitComment => None case _: CommitComment => None
@@ -74,7 +74,7 @@
<li><a href="#commits">Commits <span class="badge">@commits.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> <li><a href="#files">Files Changed <span class="badge">@diffs.size</span></a></li>
</ul> </ul>
<div class="tab-content fill-width pull-left"> <div class="tab-content fill-width" style="padding-top: 20px;">
<div class="tab-pane" id="conversation"> <div class="tab-pane" id="conversation">
@flash.get("error").map{ error => @flash.get("error").map{ error =>
<div class="alert alert-error">@error</div> <div class="alert alert-error">@error</div>

View File

@@ -8,7 +8,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${(repository.name :: pathList).mkString("/")} at ${encodeRefName(branch)} - ${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${(repository.name :: pathList).mkString("/")} at ${encodeRefName(branch)} - ${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("files", repository){
<div class="head"> <div class="head">
<div class="pull-right hide-if-blame"><div class="btn-group"> <div class="pull-right hide-if-blame"><div class="btn-group">
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t">Find file</a> <a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t">Find file</a>
@@ -48,7 +48,7 @@
} }
</div> </div>
<div class="box-header"> <div class="box-header">
@avatar(latestCommit, 20) @avatar(latestCommit, 28)
@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong") @user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
<span class="muted">@helper.html.datetimeago(latestCommit.commitTime)</span> <span class="muted">@helper.html.datetimeago(latestCommit.commitTime)</span>
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a> <a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>

View File

@@ -4,9 +4,8 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("branches", repository){
<h1>Branches</h1> <table class="table table-bordered table-hover branches">
<table class="table table-bordered table-hover table-issues branches">
<thead> <thead>
<tr> <tr>
<th style="background: #f5f5f5;color: #666;">All branches</th> <th style="background: #f5f5f5;color: #666;">All branches</th>

View File

@@ -11,7 +11,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(commit.shortMessage, Some(repository)){ @html.main(commit.shortMessage, Some(repository)){
@html.menu("code", repository){ @html.menu("files", repository){
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<th class="box-header"> <th class="box-header">

View File

@@ -8,7 +8,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("files", repository){
<div class="head"> <div class="head">
@if(pathList.isEmpty){ @if(pathList.isEmpty){
@helper.html.branchcontrol( @helper.html.branchcontrol(
@@ -33,47 +33,47 @@
} }
} }
</div> </div>
<div class="commit-list">
@commits.map { day => <table class="table table-bordered">
<div class="muted" style="background-color: white;"> @commits.map { day =>
<i class="octicon octicon-git-commit"></i> Commits on @date(day.head.commitTime) <tr>
</div> <th rowspan="@day.size" width="100">@date(day.head.commitTime)</th>
<div class="list-group box-commits"> @day.zipWithIndex.map { case (commit, i) =>
@day.map { commit => @if(i != 0){ <tr> }
<div class="list-group-item"> <td>
<ul class="nav nav-pills pull-right"> <div class="pull-right text-right">
<li><a href="@url(repository)/commit/@commit.id" class="link monospace" style="line-height: 16px;">@commit.id.substring(0, 7)</a></li> <a href="@url(repository)/commit/@commit.id" class="monospace commit-message strong"><i class="octicon octicon-diff" style="color: black;"></i>@commit.id.substring(0, 7)</a><br>
<li><a href="@url(repository)/tree/@commit.id" style="line-height: 16px;"><i class="octicon octicon-code link"></i></a></li> <a href="@url(repository)/tree/@commit.id" class="button-link">Browse files »</a>
</ul> </div>
<div>
<div class="commit-avatar-image">@avatarLink(commit, 40)</div>
<div> <div>
<div class="commit-avatar-image">@avatarLink(commit, 40)</div> <a href="@url(repository)/commit/@commit.id" class="commit-message strong">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
}
<br>
@if(commit.description.isDefined){
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
}
<div> <div>
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a> @user(commit.authorName, commit.authorEmailAddress, "username")
@if(commit.description.isDefined){ <span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a> @if(commit.isDifferentFromAuthor) {
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
} }
<br>
@if(commit.description.isDefined){
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
}
<div style="margin-top: 2px;">
@user(commit.authorName, commit.authorEmailAddress, "username")
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
@if(commit.isDifferentFromAuthor) {
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
}
</div>
</div> </div>
</div> </div>
</div> </div>
} </td>
</div> </tr>
} }
</div> }
</table>
<nav style="text-align: center; margin-top: 30px"> <nav style="text-align: center; margin-top: 20px">
<ul class="pagination"> <ul class="pagination">
@if(page <= 1){ @if(page <= 1){
<li class="disabled"><span>Newer</span></li> <li class="disabled"><span>Newer</span></li>

View File

@@ -6,7 +6,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"Deleting ${path} at ${fileName} - ${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"Deleting ${path} at ${fileName} - ${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("files", repository){
<form method="POST" action="@url(repository)/remove" validate="true"> <form method="POST" action="@url(repository)/remove" validate="true">
<div class="head"> <div class="head">
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / <a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /

View File

@@ -7,7 +7,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(if(fileName.isEmpty) "New File" else s"Editing ${fileName.get} at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) { @html.main(if(fileName.isEmpty) "New File" else s"Editing ${fileName.get} at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("files", repository){
@if(protectedBranch){ @if(protectedBranch){
<div class="alert alert-danger">branch @branch is protected.</div> <div class="alert alert-danger">branch @branch is protected.</div>
} }
@@ -18,7 +18,7 @@
@pathList.zipWithIndex.map { case (section, i) => @pathList.zipWithIndex.map { case (section, i) =>
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> / <a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
} }
<input type="text" name="newFileName" id="newFileName" placeholder="Name your file..." value="@fileName"/> <input type="text" name="newFileName" id="newFileName" class="form-control" placeholder="Name your file..." value="@fileName"/>
<input type="hidden" name="oldFileName" id="oldFileName" value="@fileName"/> <input type="hidden" name="oldFileName" id="oldFileName" value="@fileName"/>
<input type="hidden" name="branch" id="branch" value="@branch"/> <input type="hidden" name="branch" id="branch" value="@branch"/>
<input type="hidden" name="path" id="path" value="@pathList.mkString("/")"/> <input type="hidden" name="path" id="path" value="@pathList.mkString("/")"/>
@@ -27,7 +27,7 @@
<tr> <tr>
<th> <th>
<div class="pull-right"> <div class="pull-right">
<select id="wrap" class="form-control" style="margin-bottom: 0px; height: 26px; padding: 0px;"> <select id="wrap" class="form-control" style="margin-bottom: 0px; padding: 0px;">
<optgroup label="Line Wrap Mode"> <optgroup label="Line Wrap Mode">
<option value="false">No wrap</option> <option value="false">No wrap</option>
<option value="true">Soft wrap</option> <option value="true">Soft wrap</option>
@@ -58,11 +58,11 @@
</div> </div>
<div style="text-align: right;"> <div style="text-align: right;">
@if(fileName.isEmpty){ @if(fileName.isEmpty){
<a href="@url(repository)/tree/@encodeRefName(branch)/@{pathList.mkString("/")}" class="btn btn-lg btn-danger">Cancel</a> <a href="@url(repository)/tree/@encodeRefName(branch)/@{pathList.mkString("/")}" class="btn btn-danger">Cancel</a>
} else { } else {
<a href="@url(repository)/blob/@encodeRefName(branch)/@{(pathList ++ Seq(fileName.get)).mkString("/")}" class="btn btn-lg btn-danger">Cancel</a> <a href="@url(repository)/blob/@encodeRefName(branch)/@{(pathList ++ Seq(fileName.get)).mkString("/")}" class="btn btn-danger">Cancel</a>
} }
<input type="submit" id="commit" class="btn btn-lg btn-success" value="Commit changes" disabled="true"/> <input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
<input type="hidden" id="charset" name="charset" value="@content.charset"/> <input type="hidden" id="charset" name="charset" value="@content.charset"/>
<input type="hidden" id="lineSeparator" name="lineSeparator" value="@content.lineSeparator"/> <input type="hidden" id="lineSeparator" name="lineSeparator" value="@content.lineSeparator"/>
<input type="hidden" id="content" name="content" value=""/> <input type="hidden" id="content" name="content" value=""/>

View File

@@ -1,7 +1,6 @@
@(branch: String, @(branch: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
pathList: List[String], pathList: List[String],
groupNames: List[String],
latestCommit: gitbucket.core.util.JGitUtil.CommitInfo, latestCommit: gitbucket.core.util.JGitUtil.CommitInfo,
files: List[gitbucket.core.util.JGitUtil.FileInfo], files: List[gitbucket.core.util.JGitUtil.FileInfo],
readme: Option[(List[String], String)], readme: Option[(List[String], String)],
@@ -22,17 +21,25 @@
} else { } else {
s"${(repository.name :: pathList).mkString("/")} at ${encodeRefName(branch)} - ${repository.owner}/${repository.name}" s"${(repository.name :: pathList).mkString("/")} at ${encodeRefName(branch)} - ${repository.owner}/${repository.name}"
}, Some(repository)) { }, Some(repository)) {
@html.menu("code", repository, Some(branch), pathList.isEmpty, groupNames.isEmpty, info, error){ @html.menu("files", repository, Some(branch), info, error){
<div class="head"> <div class="head">
@if(pathList.isEmpty){ <div class="pull-right pc">
<div class="pull-right pc"> <div class="btn-group">
@if(platform != "linux" && platform != null){ <a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default pc" data-hotkey="t"><i class="octicon octicon-search"></i></a>
<a href="@openRepoUrl(repository.httpUrl)" id="repository-clone-url" class="btn btn-sm btn-default"><i class="octicon octicon-desktop-download"></i></a> <a href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i></a>
}
<a href="@{url(repository)}/archive/@{encodeRefName(branch)}.zip" class="btn btn-sm btn-default">Download ZIP</a>
</div> </div>
</div>
@if(pathList.isEmpty){
@if(platform != "linux" && platform != null){
<div class="pull-right pc" style="margin-right: 5px;">
<div class="btn-group">
<a href="@openRepoUrl(repository.httpUrl)" id="repository-clone-url" class="btn btn-sm btn-default"><i class="octicon octicon-desktop-download"></i></a>
<a href="@{url(repository)}/archive/@{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"> <div class="pull-right pc">
<div style="width: 240px; margin-top: 2px; margin-right: 5px; margin-left: 5px;"> <div style="width: 240px; margin-right: 5px; margin-left: 5px;">
@helper.html.copy("repository-url-copy", repository.httpUrl){ @helper.html.copy("repository-url-copy", repository.httpUrl){
@if(repository.sshUrl.isDefined){ @if(repository.sshUrl.isDefined){
<div class="btn-group input-group-btn"> <div class="btn-group input-group-btn">
@@ -60,35 +67,29 @@
</div> </div>
</div> </div>
} }
<div class="pull-right">
<div class="btn-group">
<a href="@url(repository)/new/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here" @if(!hasWritePermission){disabled}>New file</i></a>
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default pc" data-hotkey="t">Find file</a>
@if(pathList.nonEmpty){
<a href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default" data-toggle="tooltip" data-placement="bottom" title="Browse commits for this branch">History</a>
}
</div>
</div>
@helper.html.branchcontrol(branch, repository, hasWritePermission){ @helper.html.branchcontrol(branch, repository, hasWritePermission){
@repository.branchList.map { x => @repository.branchList.map { x =>
<li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li> <li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
} }
} }
@if(pathList.isEmpty){ @if(pathList.isEmpty){
@*
@branchPullRequest.map{ case (pullRequest, issue) => @branchPullRequest.map{ case (pullRequest, issue) =>
<a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-sm btn-pullrequest-branch" title="@issue.title" data-toggle="tooltip">View #@pullRequest.issueId</a> <a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-sm btn-pullrequest-branch" title="@issue.title" data-toggle="tooltip">View #@pullRequest.issueId</a>
}.getOrElse { }.getOrElse {
<a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-sm btn-success" @if(loginAccount.isEmpty){disabled}>New pull request</a> <a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-sm btn-success" @if(loginAccount.isEmpty){disabled}>New pull request</a>
} }
*@
} else { } else {
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / <a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
@pathList.zipWithIndex.map { case (section, i) => @pathList.zipWithIndex.map { case (section, i) =>
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> / <a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
} }
} }
<a href="@url(repository)/new/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here" @if(!hasWritePermission){disabled}><i class="octicon octicon-plus"></i></a>
</div> </div>
<table class="table table-file-list"> <table class="table table-hover">
@*
<tr> <tr>
<th colspan="4" style="font-weight: normal; border: none;"> <th colspan="4" style="font-weight: normal; border: none;">
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a> <a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
@@ -98,8 +99,9 @@
} }
</th> </th>
</tr> </tr>
*@
<tr> <tr>
<td colspan="4" class="latest-commit"> <th colspan="4" class="latest-commit">
<div> <div>
<div class="pull-right align-right monospace" style="line-height: 18px;"> <div class="pull-right align-right monospace" style="line-height: 18px;">
<a href="@url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> @latestCommit.id.substring(0, 10)</a> <a href="@url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> @latestCommit.id.substring(0, 10)</a>
@@ -119,7 +121,7 @@
} }
</div> </div>
</div> </div>
</td> </th>
</tr> </tr>
@if(pathList.size > 0){ @if(pathList.size > 0){
<tr> <tr>

View File

@@ -1,12 +1,11 @@
@(branch: String, @(branch: String,
treeId: String, treeId: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo
groupNames: List[String]
)(implicit context: gitbucket.core.controller.Context) )(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository, Some(branch), false, groupNames.isEmpty){ @html.menu("files", repository, Some(branch)){
<div> <div>
<div class="find-input"> <div class="find-input">

View File

@@ -1,14 +1,28 @@
@(originRepository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo], @(originRepository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo],
members: List[(String, String)], members: List[(String, String)],
groupNames: List[String],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("network", repository){ @html.menu("fork", repository){
<ul class="nav nav-tabs fill-width pull-left"> <h1>
<li class="active"><a href="@url(repository)/network/members">Members</a></li> Forked repositories
</ul> @if(loginAccount.isEmpty){
<h3>Members of the @repository.name Network</h3> <a href="@path/signin?redirect=@urlEncode(s"${path}/${repository.owner}/${repository.name}")" class="btn btn-success">Fork</a>
} else {
@if(groupNames.isEmpty) {
<a id="fork-link" href="javascript:void(0);" class="btn btn-success">Fork</a>
} else {
<a href="@path/@repository.owner/@repository.name/fork" class="btn btn-success" rel="facebox">Fork</a>
}
}
</h1>
@if(loginAccount.isDefined && groupNames.isEmpty){
<form id="fork-form" method="post" action="@path/@repository.owner/@repository.name/fork" style="display: none;">
<input type="hidden" name="account" value="@loginAccount.get.userName"/>
</form>
}
<div class="block"> <div class="block">
@if(originRepository.isDefined){ @if(originRepository.isDefined){
@avatar(originRepository.get.owner, 20) @avatar(originRepository.get.owner, 20)
@@ -33,3 +47,26 @@
} }
} }
} }
<script>
$(function(){
$('a[rel*=facebox]').facebox({
'loadingImage': '@assets/vendors/facebox/loading.gif',
'closeImage': '@assets/vendors/facebox/closelabel.png'
});
$(document).on("click", ".js-fork-owner-select-target", function() {
if (!$(this).hasClass("disabled")) {
var account = $(this).text().replace("@@", "");
$("#account").val(account);
$("#fork").submit();
}
});
@if(loginAccount.isDefined){
$(document).on("click", "a#fork-link", function(e) {
e.preventDefault();
$('#fork-form').submit();
});
}
});
</script>

View File

@@ -4,7 +4,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("files", repository){
@if(!hasWritePermission){ @if(!hasWritePermission){
<h3>This is an empty repository</h3> <h3>This is an empty repository</h3>
} else { } else {

View File

@@ -2,8 +2,7 @@
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("tags", repository){
<h1>Tags</h1>
<table class="table table-bordered"> <table class="table table-bordered">
<thead> <thead>
<tr> <tr>

View File

@@ -1,5 +1,6 @@
@(files: List[gitbucket.core.service.RepositorySearchService.FileSearchResult], @(files: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
issueCount: Int, issueCount: Int,
wikiCount: Int,
query: String, query: String,
page: Int, page: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@@ -7,7 +8,7 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import gitbucket.core.service.RepositorySearchService._ @import gitbucket.core.service.RepositorySearchService._
@html.main("Search Results", Some(repository)){ @html.main("Search Results", Some(repository)){
@menu("code", files.size, issueCount, query, repository){ @menu("code", files.size, issueCount, wikiCount, query, repository){
@if(files.isEmpty){ @if(files.isEmpty){
<h4>We couldn't find any code matching '@query'</h4> <h4>We couldn't find any code matching '@query'</h4>
} else { } else {

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