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 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
--------
@@ -56,10 +59,28 @@ Support
- 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.
- 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
--------
### 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
- New GitHub UI
- Improve mobile view

View File

@@ -1,6 +1,6 @@
val Organization = "gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "3.12.0"
val GitBucketVersion = "3.14.0"
val ScalatraVersion = "2.4.0"
val JettyVersion = "9.3.6.v20151106"
@@ -10,7 +10,7 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.11.7"
scalaVersion := "2.11.8"
// dependency settings
resolvers ++= Seq(
@@ -18,6 +18,7 @@ resolvers ++= Seq(
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
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.archive" % "4.1.1.201511131810-r",
"org.scalatra" %% "scalatra" % ScalatraVersion,
@@ -25,7 +26,7 @@ libraryDependencies ++= Seq(
"org.json4s" %% "json4s-jackson" % "3.3.0",
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
"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-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
@@ -38,7 +39,6 @@ libraryDependencies ++= Seq(
"com.mchange" % "c3p0" % "0.9.5.2",
"com.typesafe" % "config" % "1.3.0",
"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",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
@@ -52,7 +52,7 @@ libraryDependencies ++= Seq(
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
// 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")
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console")

View File

@@ -10,7 +10,7 @@
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>1.0-beta-6</version>
<version>2.10</version>
</extension>
</extensions>
</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){
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 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{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,24 @@
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.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.servlet.{MultipartConfig, FileUploadSupport, FileItem}
import org.apache.commons.io.FileUtils
import org.apache.commons.io.{IOUtils, FileUtils}
/**
* Provides Ajax based file upload functionality.
*
* 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)))
@@ -31,6 +37,54 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
}, 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 {
case Some(file) if(mimeTypeChcker(file.name)) =>
defining(FileUtil.generateFileId){ fileId =>

View File

@@ -1,9 +1,8 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.helper.xml
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.ControlUtil._
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
@@ -110,7 +109,7 @@ trait IndexControllerBase extends ControllerBase {
get("/_user/proposals")(usersOnly {
contentType = formats("json")
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 {
case "issue" => gitbucket.core.search.html.issues(
searchIssues(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)
case _ => gitbucket.core.search.html.code(
searchFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query),
countWikiPages(repository.owner, repository.name, query),
query, page, repository)
}
}

View File

@@ -67,7 +67,7 @@ trait IssuesControllerBase extends ControllerBase {
_,
getComments(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),
getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount),
@@ -79,7 +79,7 @@ trait IssuesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
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),
getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount),
@@ -380,7 +380,7 @@ trait IssuesControllerBase extends ControllerBase {
"issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
if(!getAccountByUserName(owner).exists(_.groupAccount)){
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted
} else {
getCollaborators(owner, repoName)

View File

@@ -23,6 +23,7 @@ trait LabelsControllerBase extends ControllerBase {
"labelColor" -> trim(label("Color", text(required, color)))
)(LabelForm.apply)
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
html.list(
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] = {
val owner = params("owner")
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))
.sortWith((a, b) => a.registeredDate before b.registeredDate),
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),
getLabels(owner, name),
commits,
@@ -178,7 +178,7 @@ trait PullRequestsControllerBase extends ControllerBase {
}
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,
"Merge branch '${alias}' into ${pullreq.requestBranch}") match {
s"Merge branch '${alias}' into ${pullreq.requestBranch}") match {
case None => // conflict
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
case Some(oldId) =>
@@ -370,7 +370,7 @@ trait PullRequestsControllerBase extends ControllerBase {
originRepository,
forkedRepository,
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),
getLabels(originRepository.owner, originRepository.name)
)
@@ -524,7 +524,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
page,
if(!getAccountByUserName(owner).exists(_.groupAccount)){
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted
} else {
getCollaborators(owner, repoName)

View File

@@ -15,6 +15,7 @@ import org.scalatra.i18n.Messages
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId
import gitbucket.core.model.WebHookContentType
class RepositorySettingsController extends RepositorySettingsControllerBase
@@ -49,13 +50,16 @@ trait RepositorySettingsControllerBase extends ControllerBase {
)(CollaboratorForm.apply)
// 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(
"url" -> trim(label("url", text(required, webHook(update)))),
"events" -> webhookEvents,
"ctype" -> label("ctype", text()),
"token" -> optional(trim(label("token", text(maxlength(100)))))
)(WebHookForm.apply)
)(
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
)
// for transfer ownership
case class TransferOwnerShipForm(newOwner: String)
@@ -87,7 +91,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
repository.name,
form.description,
repository.repository.parentUserName.map { _ =>
repository.repository.`private`
repository.repository.isPrivate
} getOrElse form.isPrivate
)
// Change repository name
@@ -148,7 +152,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
html.collaborators(
getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.groupAccount,
getAccountByUserName(repository.owner).get.isGroupAccount,
repository)
})
@@ -156,7 +160,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the collaborator.
*/
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)
}
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
@@ -166,7 +170,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the collaborator.
*/
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"))
}
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
@@ -183,7 +187,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook edit page.
*/
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)
})
@@ -191,7 +195,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the web hook URL.
*/
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"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})
@@ -221,7 +225,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
val url = params("url")
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 ownerAccount = getAccountByUserName(repository.owner).get
val commits = if(repository.commitCount == 0) List.empty else git.log
@@ -280,7 +285,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Update web hook settings.
*/
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"
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] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) if(x.groupAccount)
case Some(x) if(x.isGroupAccount)
=> Some("User does not exist.")
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
=> Some("User can access this repository already.")

View File

@@ -497,6 +497,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
getForkedRepositories(
repository.repository.originUserName.getOrElse(repository.owner),
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)
})
@@ -507,13 +511,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val ref = multiParams("splat").head
JGitUtil.getTreeId(git, ref).map{ treeId =>
html.find(ref,
treeId,
repository,
context.loginAccount match {
case None => List()
case account: Option[Account] => getGroupsByUserName(account.get.userName)
})
html.find(ref, treeId, repository)
} getOrElse NotFound
}
})
@@ -575,10 +573,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.files(revision, repository,
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
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),

View File

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

View File

@@ -12,7 +12,8 @@ import org.eclipse.jgit.api.Git
import org.scalatra.i18n.Messages
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 {
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 mailAddress = column[String]("MAIL_ADDRESS")
val password = column[String]("PASSWORD")
val administrator = column[Boolean]("ADMINISTRATOR")
val isAdmin = column[Boolean]("ADMINISTRATOR")
val url = column[String]("URL")
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE")
@@ -19,7 +19,7 @@ trait AccountComponent { self: Profile =>
val image = column[String]("IMAGE")
val groupAccount = column[Boolean]("GROUP_ACCOUNT")
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,
mailAddress: String,
password: String,
administrator: Boolean,
isAdmin: Boolean,
url: Option[String],
registeredDate: java.util.Date,
updatedDate: java.util.Date,
lastLoginDate: Option[java.util.Date],
image: Option[String],
groupAccount: Boolean,
removed: Boolean
isGroupAccount: Boolean,
isRemoved: Boolean
)

View File

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

View File

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

View File

@@ -3,21 +3,42 @@ package gitbucket.core.model
trait WebHookComponent extends TemplateComponent { self: Profile =>
import profile.simple._
implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
lazy val WebHooks = TableQuery[WebHooks]
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
val url = column[String]("URL")
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)
}
}
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(
userName: String,
repositoryName: String,
url: String,
ctype: WebHookContentType,
token: Option[String]
)

View File

@@ -1,16 +1,18 @@
package gitbucket.core.plugin
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.util.ControlUtil._
import gitbucket.core.util.Version
/**
* 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 pluginName: String
@@ -77,6 +79,76 @@ trait Plugin {
*/
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.
* Register plugin functionality to PluginRegistry.
@@ -100,6 +172,27 @@ trait Plugin {
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { 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.net.URLClassLoader
import javax.servlet.ServletContext
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.model.Account
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings
@@ -33,6 +33,14 @@ class PluginRegistry {
private val receiveHooks = new ListBuffer[ReceiveHook]
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 = {
plugins += pluginInfo
}
@@ -107,17 +115,47 @@ class PluginRegistry {
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
private case class GlobalAction(
method: String,
path: String,
function: (HttpServletRequest, HttpServletResponse, Context) => Any
)
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = {
globalMenus += globalMenu
}
private case class RepositoryAction(
method: String,
path: String,
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any
)
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = {
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(
pluginId: String,
pluginName: String,

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ trait RepositoryCreationService {
insertRepository(name, owner, description, isPrivate)
// Add collaborators for group repository
if(ownerAccount.groupAccount){
if(ownerAccount.isGroupAccount){
getGroupMembers(owner).foreach { member =>
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 objectId = git.getRepository.resolve("HEAD")
val revCommit = revWalk.parseCommit(objectId)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,7 +30,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
)
.distinct
.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._
@html.main("Applications"){
<div class="container body">
<div class="row">
<div class="col-md-3">
@menu("application", settings.ssh)
</div>
<div class="col-md-9">
<div class="panel panel-default">
<div class="panel-heading strong">Personal access tokens</div>
<div class="panel-body">
@if(personalTokens.isEmpty && gneratedToken.isEmpty){
No tokens.
} else {
Tokens you have generated that can be used to access the GitBucket API.
<hr style="margin-top: 10px;">
}
@gneratedToken.map{ case (token, tokenString) =>
<div class="alert alert-info">
Make sure to copy your new personal access token now. You won't be able to see it again!
</div>
<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;">
@menu("application", settings.ssh){
<div class="panel panel-default">
<div class="panel-heading strong">Personal access tokens</div>
<div class="panel-body">
@if(personalTokens.isEmpty && gneratedToken.isEmpty){
No tokens.
} else {
Tokens you have generated that can be used to access the GitBucket API.
<hr style="margin-top: 10px;">
}
@gneratedToken.map{ case (token, tokenString) =>
<div class="alert alert-info">
Make sure to copy your new personal access token now. You won't be able to see it again!
</div>
<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>
}
<strong>@token.note</strong>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
</div>
<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>
<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>
</form>
</div>
</div>
</form>
}
</div>
}

View File

@@ -4,14 +4,10 @@
@import gitbucket.core.view.helpers._
@html.main("Edit your profile"){
<div class="container body">
<div class="row">
<div class="col-md-3">
@menu("profile", settings.ssh)
</div>
<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">
@menu("profile", settings.ssh){
@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-heading strong">Profile</div>
<div class="panel-body">
@@ -49,17 +45,17 @@
</fieldset>
</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 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>
</div>
}
</div>
}
<script>

View File

@@ -2,7 +2,7 @@
@import context._
@import gitbucket.core.view.helpers._
@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">
<div class="row">
<div class="col-md-5">
@@ -32,7 +32,7 @@
</ul>
@helper.html.account("memberName", 200)
<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>
<span class="error" id="error-members"></span>
</div>
@@ -42,12 +42,12 @@
<fieldset class="margin">
@if(account.isDefined){
<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>
}
<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){
<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>
</form>
@@ -103,22 +103,22 @@ $(function(){
});
@members.map { member =>
addMemberHTML('@member.userName', @member.manager);
addMemberHTML('@member.userName', @member.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){
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){
managerButton.addClass('active');
}
$('#member-list').append($('<li>')
.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(managerButton))
.append(' ')
@@ -130,9 +130,7 @@ $(function(){
function updateMembers(){
var members = $('#member-list li').map(function(i, e){
var userName = $(e).data('name');
return userName + ':' + $('button.active').filter(function(i, e){
return $(e).data('name') == userName;
}).attr('value');
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
}).get().join(',');
$('#members').val(members);
}

View File

@@ -4,56 +4,56 @@
@import gitbucket.core.view.helpers._
@html.main(account.userName){
<div class="container body">
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
<div class="block">
<div class="account-image">@avatar(account.userName, 270)</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 class="main-sidebar">
<div class="block">
<div class="account-image">@avatar(account.userName, 240)</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="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>
}

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._
<div class="box">
<ul class="nav nav-tabs nav-stacked side-menu">
<div class="main-sidebar">
<ul class="nav nav-pills nav-stacked">
<li@if(active=="profile"){ class="active"}>
<a href="@path/@loginAccount.get.userName/_edit">Profile</a>
</li>
@@ -13,5 +13,15 @@
<li@if(active=="application"){ class="active"}>
<a href="@path/@loginAccount.get.userName/_application">Applications</a>
</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>
</div>
<div class="main-content">
@body
</div>

View File

@@ -3,7 +3,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
@import context._
@import gitbucket.core.view.helpers._
@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>
<p class="muted">
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>
</fieldset>
<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>
</form>
</div>

View File

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

View File

@@ -4,45 +4,40 @@
@import gitbucket.core.view.helpers._
@html.main("SSH Keys"){
<div class="container body">
<div class="row">
<div class="col-md-3">
@menu("ssh", settings.ssh)
@menu("ssh", settings.ssh){
<div class="panel panel-default">
<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 class="col-md-9">
<form method="POST" action="@path/@account.userName/_ssh" validate="true">
<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">
@if(sshKeys.isEmpty){
No keys
}
@sshKeys.zipWithIndex.map { case (key, i) =>
@if(i != 0){
<hr style="margin-top: 10px;">
}
<strong>@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>
}
<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 method="POST" action="@path/@account.userName/_ssh" validate="true">
<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>
</form>
}
</div>
}

View File

@@ -1,25 +1,30 @@
@(active: String)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
<div class="container body">
<div class="row">
<div class="col-md-3">
<ul class="nav nav-tabs nav-stacked side-menu" id="system-admin-menu-container">
<li@if(active=="users"){ class="active"}>
<a href="@path/admin/users">User Management</a>
</li>
<li@if(active=="system"){ class="active"}>
<a href="@path/admin/system">System Settings</a>
</li>
<li@if(active=="plugins"){ class="active"}>
<a href="@path/admin/plugins">Plugins</a>
</li>
<li>
<a href="@path/console/login.jsp">H2 Console</a>
</li>
</ul>
</div>
<div class="col-md-9">
@body
</div>
<div class="main-sidebar">
<ul class="nav nav-pills nav-stacked" id="system-admin-menu-container">
<li@if(active=="users"){ class="active"}>
<a href="@path/admin/users">User Management</a>
</li>
<li@if(active=="system"){ class="active"}>
<a href="@path/admin/system">System Settings</a>
</li>
<li@if(active=="plugins"){ class="active"}>
<a href="@path/admin/plugins">Plugins</a>
</li>
<li>
<a href="@path/console/login.jsp">H2 Console</a>
</li>
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
@menu(context).map { link =>
<li@if(active==link.id){ class="active"}>
<a href="@path/@link.path">@link.label</a>
</li>
}
}
</ul>
</div>
<div class="main-content">
@body
</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}/>
@if(account.isDefined){
<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
</label>
<div>
@@ -53,10 +53,10 @@
<fieldset class="form-group">
<label class="strong">User Type:</label>
<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 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>
</fieldset>
<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}/>
@if(account.isDefined){
<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
</label>
}
@@ -38,7 +38,7 @@
</ul>
@helper.html.account("memberName", 200)
<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>
<span class="error" id="error-members"></span>
</div>
@@ -98,7 +98,7 @@ $(function(){
});
@members.map { member =>
addMemberHTML('@member.userName', @member.manager);
addMemberHTML('@member.userName', @member.isManager);
}
function addMemberHTML(userName, isManager){

View File

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

View File

@@ -4,18 +4,8 @@
groups: List[String])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
<span class="small">
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL">
<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){
<div id="table-issues-control">
@helper.html.dropdown("Visibility"){
<li>
<a href="@(condition.copy(visibility = (if(condition.visibility == Some("private")) None else Some("private"))).toURL)">
@helper.html.checkicon(condition.visibility == Some("private"))
@@ -29,7 +19,7 @@
</a>
</li>
}
@helper.html.dropdown("Organization", flat = true){
@helper.html.dropdown("Organization"){
@groups.map { group =>
<li>
<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>
}
}
@helper.html.dropdown("Sort", flat = true){
@helper.html.dropdown("Sort"){
<li>
<a href="@condition.copy(sort="created", direction="desc").toURL">
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest

View File

@@ -4,15 +4,17 @@
closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
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 gitbucket.core.view.helpers._
@html.main("Issues"){
<div class="body">
@sidebar(recentRepositories, userRepositories){
@dashboard.html.tab("issues")
<div class="container">
@issuesnavi(filter, "issues", condition)
@issuesnavi(filter, openCount, closedCount, condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div>
</div>
}
}

View File

@@ -21,11 +21,6 @@
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
<tr>
<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;
@if(issue.isPullRequest){
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
@@ -50,7 +45,7 @@
</a>
}
</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)
@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>

View File

@@ -1,9 +1,17 @@
@(filter: String,
active: String,
openCount: Int,
closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition)(implicit context: gitbucket.core.controller.Context)
@import context._
@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}">
<a href="@path/dashboard/@active/created_by@condition.copy(author = None, assigned = None).toURL">Created</a>
</li>
@@ -13,8 +21,5 @@
<li class="@if(filter == "mentioned"){active}">
<a href="@path/dashboard/@active/mentioned@condition.copy(author = None, assigned = None).toURL">Mentioned</a>
</li>
*@
</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,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
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 gitbucket.core.view.helpers._
@html.main("Pull Requests"){
<div class="body">
@sidebar(recentRepositories, userRepositories){
@dashboard.html.tab("pulls")
<div class="container">
@issuesnavi(filter, "pulls", condition)
@issuesnavi(filter, openCount, closedCount, condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</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)
@import context._
@import gitbucket.core.view.helpers._
<div class="dashboard-nav">
<div class="container">
<a href="@path/" @if(active == ""){ class="active"}>
<i class="octicon octicon-rss"></i> News Feed
</a>
@if(loginAccount.isDefined){
<a href="@path/dashboard/pulls" @if(active == "pulls" ){ class="active"}>
<i class="octicon octicon-git-pull-request"></i> Pull Requests
</a>
<a href="@path/dashboard/issues" @if(active == "issues"){ class="active"}>
<i class="octicon octicon-issue-opened"></i> Issues
</a>
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
<li @if(active == ""){ class="active"}><a href="@path/">News Feed</a></li>
@if(loginAccount.isDefined){
<li @if(active == "pulls" ){ class="active"}><a href="@path/dashboard/pulls">Pull Requests</a></li>
<li @if(active == "issues"){ class="active"}><a href="@path/dashboard/issues">Issues</a></li>
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
@tab(context).map { link =>
<li @if(active == link.id){ class="active"}><a href="@path/@link.path">@link.label</a></li>
}
}
</div>
</div>
<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>
}
</ul>

View File

@@ -61,8 +61,10 @@
}
@detailActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<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="strong">
@avatar(activity.activityUserName, 16)
@@ -75,8 +77,10 @@
}
@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-content">
*@
<div>
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@avatar(activity.activityUserName, 16)
@@ -87,12 +91,14 @@
}
@simpleActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<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>
@avatar(activity.activityUserName, 16)
@activityMessage(activity.message)
<span class="muted small">@helper.html.datetimeago(activity.activityDate)</span>
</div>
</div>
}

View File

@@ -3,12 +3,12 @@
<div class="input-group" style="margin-bottom: 0px;">
@html
<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>
</span>
</div>
} 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>
}
<script>

View File

@@ -49,8 +49,8 @@
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
@if(newCommitId.isDefined){
<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="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="ignore-whitespace" value="1"/> Ignore Space</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>
</div>
}
@@ -60,8 +60,8 @@
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
@if(newCommitId.isDefined){
<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="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="ignore-whitespace" value="1"/> Ignore Space</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>
</div>
}
@@ -77,7 +77,7 @@
@if(diff.changeType == ChangeType.DELETE){
@if(oldCommitId.isDefined){
<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>
</div>
}

View File

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

View File

@@ -15,7 +15,7 @@
@import gitbucket.core._
@import gitbucket.core.view.helpers._
<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><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li>
</ul>

View File

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

View File

@@ -4,91 +4,19 @@
@import context._
@import gitbucket.core.view.helpers._
@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()
<div class="container">
<div class="row">
<div class="col-md-8">
<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 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>
}
}
<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">
<input type="hidden" name="issueId" value="@issue.issueId"/>
@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>

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
</div>
<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>
<h1>
<span class="show-title">
@@ -46,12 +46,12 @@
</span>
</div>
<hr>
<div class="row" style="margin-top: 15px;">
<div class="col-md-10">
<div class="row-fluid" style="margin-top: 15px;">
<div class="col-md-9">
@commentlist(Some(issue), comments, hasWritePermission, repository)
@commentform(issue, true, hasWritePermission, repository)
</div>
<div class="col-md-2">
<div class="col-md-3">
@issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div>
</div>

View File

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

View File

@@ -1,6 +1,6 @@
@(issueLabels: List[gitbucket.core.model.Label])
@if(issueLabels.isEmpty){
<li><span class="muted small">None yet</span></li>
<li><span class="muted">None yet</span></li>
}
@issueLabels.map { label =>
<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 gitbucket.core.view.helpers._
@html.main(s"Labels - ${repository.owner}/${repository.name}"){
@html.menu("issues", repository){
@issues.html.navigation("labels", hasWritePermission, repository)
<br>
@html.menu("labels", repository){
@if(loginAccount.isDefined){
<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;">
<tr><td></td></tr>
</table>

View File

@@ -13,7 +13,24 @@
@import gitbucket.core.view.helpers._
@html.main((if(target == "issues") "Issues" else "Pull requests") + s" - ${repository.owner}/${repository.name}", Some(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)
@if(hasWritePermission){
<form id="batcheditForm" method="POST">

View File

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

View File

@@ -3,13 +3,10 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){
@html.menu("issues", repository){
@html.menu("milestones", repository){
@if(milestone.isEmpty){
<h4>New milestone</h4>
<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"/>
<form method="POST" action="@url(repository)/issues/milestones/@if(milestone.isEmpty){new}else{@milestone.get.milestoneId/edit}" validate="true">
@@ -30,7 +27,7 @@
<hr>
<div class="pull-right">
@if(milestone.isEmpty){
<input type="submit" class="btn btn-default" value="Create milestone"/>
<input type="submit" class="btn btn-success" value="Create milestone"/>
} else {
@if(milestone.get.closedDate.isDefined){
<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"
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>
</form>

View File

@@ -5,20 +5,21 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main(s"Milestones - ${repository.owner}/${repository.name}"){
@html.menu("issues", repository){
@issues.html.navigation("milestones", hasWritePermission, repository)
<br>
@html.menu("milestones", repository){
@if(loginAccount.isDefined){
<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">
<thead>
<tr>
<th style="background-color: #eee;">
<span class="small">
<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
</a>&nbsp;&nbsp;
<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
</a>
</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" />
<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">
<link href="@assets/vendors/bootstrap-theme-github-3.1.1/css/bootstrap.css" rel="stylesheet">
@*
<link href="@assets/vendors/bootstrap-theme-github-3.1.1/css/bootstrap-responsive.css" rel="stylesheet">
*@
<link href="@assets/vendors/bootstrap-3.3.6/css/bootstrap.min.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/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
@@ -25,7 +22,7 @@
<script src="@assets/vendors/dropzone/dropzone.js"></script>
<script src="@assets/common/js/validation.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/datepicker/js/moment.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/jquery-hotkeys/jquery.hotkeys.js"></script>
@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" />
}
}
@@ -44,7 +41,7 @@
<body>
<form id="search" action="@path/search" method="POST" class="form-inline">
<nav class="navbar navbar-default">
<div class="container" style="width: 980px;">
<div class="container">
@* TODO: for plugi-ins?
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
@@ -53,14 +50,14 @@
</button>
*@
<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 =>
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
}
</a>
@if(loginAccount.isDefined){
@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="repository" value="@repository.name"/>
}
@@ -69,11 +66,16 @@
} else {
@* TODO: merge with below *@
@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="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){
<div class="pull-right" style="margin-top: 6px;">
<div class="btn-group" style="margin-right: 8px;">
@@ -92,7 +94,7 @@
<ul class="dropdown-menu pull-right">
<li><a href="@url(loginAccount.get.userName)">Your profile</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/signout">Sign out</a></li>
@@ -100,7 +102,7 @@
</div>
</div>
} 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>
</nav>

View File

@@ -1,20 +1,21 @@
@(active: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
id: Option[String] = None,
isRepoTop: Boolean = false,
isNoGroup: Boolean = true,
info: Option[Any] = None,
error: Option[Any] = None)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
@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"}>
<a href="@url(repository)@path">
<i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i> <span class="pc">@label</span>
@if(count > 0){
<span class="badge">@count</span>
}
<i class="menu-icon octicon octicon-@icon"></i>
<span class="pc">
@label
@if(count > 0){
<span class="badge">@count</span>
}
</span>
</a>
</li>
}
@@ -24,29 +25,6 @@
@helper.html.information(info)
@helper.html.error(error)
<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)
<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>
</div>
}
@x.description.map { description =>
<div class="normal muted" style="margin-left: 36px; font-size: 80%;">@detectAndRenderLinks(description)</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 class="container body">
@if(isRepoTop){
@repository.repository.description.map { description =>
<p class="description">@detectAndRenderLinks(description)</p>
}
<div style="margin-bottom: 10px; padding: 4px;" class="panel panel-default">
<table class="fill-width">
<tr>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link">
<i class="octicon octicon-history"></i>
@if(repository.commitCount > 10000){
<strong>10000+</strong> commits
} else {
<strong>@repository.commitCount</strong> commits
}
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/branches" class="header-link" class="header-link">
<i class="octicon octicon-git-branch"></i>
<strong>@repository.branchList.length</strong> branches
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/tags" class="header-link" class="header-link">
<i class="octicon octicon-tag"></i>
<strong>@repository.tags.length</strong> releases
</a>
</td>
</tr>
</table>
</div>
}
@body
<div class="main-sidebar">
<ul class="nav nav-pills nav-stacked">
@menuitem("" ,"files" ,"Files", "code")
@if(repository.commitCount != 0) {
@menuitem("/branches" ,"branches" ,"Branches", "git-branch", repository.branchList.length)
@menuitem("/tags" ,"tags" ,"Tags", "tag", repository.tags.length)
}
@menuitem("/issues" ,"issues" ,"Issues", "issue-opened", repository.issueCount)
@menuitem("/pulls" ,"pulls" ,"Pull Requests", "git-pull-request", repository.pullCount)
@menuitem("/issues/labels" ,"labels" ,"Labels", "tag")
@menuitem("/issues/milestones" ,"milestones" ,"Milestones", "milestone")
@menuitem("/wiki" ,"wiki" ,"Wiki", "book")
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
@menuitem("/settings" , "settings" , "Settings", "tools")
}
@gitbucket.core.plugin.PluginRegistry().getRepositoryMenus.map { menu =>
@menu(repository, context).map { link =>
@menuitem(link.path, link.id, link.label, link.icon.getOrElse("ruby"))
}
}
</ul>
</div>
<div class="main-content">
@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 gitbucket.core.view.helpers._
@import gitbucket.core.model._
<div class="commit-list">
@commits.map { day =>
<div class="muted" style="background-color: white;">
<i class="octicon octicon-git-commit"></i> Commits on @date(day.head.commitTime)
</div>
<div class="list-group box-commits">
@day.map { commit =>
<div class="list-group-item">
<ul class="nav nav-pills pull-right">
<li><a href="@url(repository)/commit/@commit.id" class="link monospace" style="line-height: 16px;">@commit.id.substring(0, 7)</a></li>
<li><a href="@url(repository)/tree/@commit.id" style="line-height: 16px;"><i class="octicon octicon-code link"></i></a></li>
</ul>
<table class="table table-bordered">
@commits.map { day =>
<tr>
<th rowspan="@day.size" width="100">@date(day.head.commitTime)</th>
@day.zipWithIndex.map { case (commit, i) =>
@if(i != 0){ <tr> }
<td>
<div class="pull-right text-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>
<a href="@url(repository)/tree/@commit.id" class="button-link">Browse files »</a>
</div>
<div>
<div class="commit-avatar-image">@avatarLink(commit, 40)</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>
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
@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>
}
<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>
</td>
</tr>
}
}
</table>

View File

@@ -54,36 +54,31 @@
<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">
<div class="row">
<div class="col-md-10">
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
<div class="panel panel-default issue-box">
<div class="panel-body">
<span class="error" id="error-title"></span>
<input type="text" name="title" class="form-control input-lg" placeholder="Title"/>
@helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true,
style = "height: 200px;"
)
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
<input type="hidden" name="targetBranch" value="@originId"/>
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
<input type="hidden" name="requestRepositoryName" value="@forkedRepository.name"/>
<input type="hidden" name="requestBranch" value="@forkedId"/>
<input type="hidden" name="commitIdFrom" value="@sourceId"/>
<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 class="col-md-9">
<span class="error" id="error-title"></span>
<input type="text" name="title" class="form-control" style="margin-bottom: 6px;" placeholder="Title"/>
@helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true,
style = "height: 200px;"
)
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
<input type="hidden" name="targetBranch" value="@originId"/>
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
<input type="hidden" name="requestRepositoryName" value="@forkedRepository.name"/>
<input type="hidden" name="requestBranch" value="@forkedId"/>
<input type="hidden" name="commitIdFrom" value="@sourceId"/>
<input type="hidden" name="commitIdTo" value="@commitId"/>
<div class="align-right">
<input type="submit" class="btn btn-success" value="Create pull request"/>
</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)
</div>
</div>

View File

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

View File

@@ -51,7 +51,7 @@
<div class="pull-right">
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/update_branch">
<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>
</div>
}
@@ -83,7 +83,7 @@
</div>
@if(status.hasMergePermission){
<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>.
<div id="command-line" style="display: none;margin-top: 15px;">
<hr />

View File

@@ -26,7 +26,7 @@
}
</div>
<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>
<h1>
<span class="show-title">
@@ -35,7 +35,7 @@
</span>
<span class="edit-title" style="display: none;">
<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>
</h1>
</div>
@@ -66,7 +66,7 @@
</span>
}
</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 @{
case comment: IssueComment => Some(comment)
case _: CommitComment => None
@@ -74,7 +74,7 @@
<li><a href="#commits">Commits <span class="badge">@commits.size</span></a></li>
<li><a href="#files">Files Changed <span class="badge">@diffs.size</span></a></li>
</ul>
<div class="tab-content fill-width pull-left">
<div class="tab-content fill-width" style="padding-top: 20px;">
<div class="tab-pane" id="conversation">
@flash.get("error").map{ error =>
<div class="alert alert-error">@error</div>

View File

@@ -8,7 +8,7 @@
@import context._
@import gitbucket.core.view.helpers._
@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="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>
@@ -48,7 +48,7 @@
}
</div>
<div class="box-header">
@avatar(latestCommit, 20)
@avatar(latestCommit, 28)
@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
<span class="muted">@helper.html.datetimeago(latestCommit.commitTime)</span>
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
@import context._
@import gitbucket.core.view.helpers._
@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">
<div class="head">
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /

View File

@@ -7,7 +7,7 @@
@import context._
@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.menu("code", repository){
@html.menu("files", repository){
@if(protectedBranch){
<div class="alert alert-danger">branch @branch is protected.</div>
}
@@ -18,7 +18,7 @@
@pathList.zipWithIndex.map { case (section, i) =>
<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="branch" id="branch" value="@branch"/>
<input type="hidden" name="path" id="path" value="@pathList.mkString("/")"/>
@@ -27,7 +27,7 @@
<tr>
<th>
<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">
<option value="false">No wrap</option>
<option value="true">Soft wrap</option>
@@ -58,11 +58,11 @@
</div>
<div style="text-align: right;">
@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 {
<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="lineSeparator" name="lineSeparator" value="@content.lineSeparator"/>
<input type="hidden" id="content" name="content" value=""/>

View File

@@ -1,7 +1,6 @@
@(branch: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
pathList: List[String],
groupNames: List[String],
latestCommit: gitbucket.core.util.JGitUtil.CommitInfo,
files: List[gitbucket.core.util.JGitUtil.FileInfo],
readme: Option[(List[String], String)],
@@ -22,17 +21,25 @@
} else {
s"${(repository.name :: pathList).mkString("/")} at ${encodeRefName(branch)} - ${repository.owner}/${repository.name}"
}, 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">
@if(pathList.isEmpty){
<div class="pull-right pc">
@if(platform != "linux" && platform != null){
<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">Download ZIP</a>
<div class="pull-right pc">
<div class="btn-group">
<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="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i></a>
</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 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){
@if(repository.sshUrl.isDefined){
<div class="btn-group input-group-btn">
@@ -60,35 +67,29 @@
</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){
@repository.branchList.map { x =>
<li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
}
}
@if(pathList.isEmpty){
@*
@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>
}.getOrElse {
<a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-sm btn-success" @if(loginAccount.isEmpty){disabled}>New pull request</a>
}
*@
} else {
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
@pathList.zipWithIndex.map { case (section, i) =>
<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>
<table class="table table-file-list">
<table class="table table-hover">
@*
<tr>
<th colspan="4" style="font-weight: normal; border: none;">
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
@@ -98,8 +99,9 @@
}
</th>
</tr>
*@
<tr>
<td colspan="4" class="latest-commit">
<th colspan="4" class="latest-commit">
<div>
<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>
@@ -119,7 +121,7 @@
}
</div>
</div>
</td>
</th>
</tr>
@if(pathList.size > 0){
<tr>

View File

@@ -1,12 +1,11 @@
@(branch: String,
treeId: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
groupNames: List[String]
repository: gitbucket.core.service.RepositoryService.RepositoryInfo
)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@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 class="find-input">

View File

@@ -1,14 +1,28 @@
@(originRepository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo],
members: List[(String, String)],
groupNames: List[String],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("network", repository){
<ul class="nav nav-tabs fill-width pull-left">
<li class="active"><a href="@url(repository)/network/members">Members</a></li>
</ul>
<h3>Members of the @repository.name Network</h3>
@html.menu("fork", repository){
<h1>
Forked repositories
@if(loginAccount.isEmpty){
<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">
@if(originRepository.isDefined){
@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 gitbucket.core.view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){
@html.menu("files", repository){
@if(!hasWritePermission){
<h3>This is an empty repository</h3>
} else {

View File

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

View File

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

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