Compare commits

..

68 Commits
2.1 ... 2.2.1

Author SHA1 Message Date
Tomofumi Tanaka
be79ac2eb2 (refs #458)Skip unexpected commit message
This patch is temporary measures.
MUST create AutoUpdate before release 2.3.
2014-08-05 00:43:20 +09:00
Tomofumi Tanaka
05afec3236 (refs#458)Correct short and full message
Swaped short and full message in commit info by accident.
2014-08-05 00:07:21 +09:00
Naoki Takezoe
57879eb72e Update README.md 2014-08-04 00:08:05 +09:00
Naoki Takezoe
2bc915f51b Disable JavaScript console 2014-08-04 00:00:41 +09:00
Naoki Takezoe
1ca55805b5 JSON response support for plug-ins 2014-08-03 23:59:56 +09:00
Naoki Takezoe
93cc1be166 (refs #374)Fix build.xml 2014-08-03 19:26:01 +09:00
Naoki Takezoe
f88ce3f671 Update version number to 2.2 2014-08-03 19:19:15 +09:00
Naoki Takezoe
20aabfc273 Merge branch 'scala-2.11' 2014-08-03 19:13:34 +09:00
shimamoto
601f8c4249 (refs #374) Fix compile error. 2014-08-03 18:41:03 +09:00
Tomofumi Tanaka
d0ccfc52b8 (refs #421)Add tar.gz archive download link 2014-08-02 21:22:49 +09:00
Tomofumi Tanaka
c22aef8ee2 (refs #421)Add tar.gz archive download
* Update jgit version
* Add new lib org.eclipse.jgit.archive
* TODO: Add link in views
2014-08-01 23:56:02 +09:00
Tomofumi Tanaka
3807e61a48 Merge branch 'show-author' 2014-07-31 22:48:15 +09:00
Naoki Takezoe
55722f87af Fix TestCase 2014-07-31 22:04:52 +09:00
Tomofumi Tanaka
212f3725ed (refs #437)Show author at blob, commits and wiki history view 2014-07-30 22:41:04 +09:00
Naoki Takezoe
82beed1f44 (refs #374)Upgrading to Scala 2.11.2 2014-07-30 07:43:32 +09:00
Naoki Takezoe
0ede7e9921 (refs #374)Upgrading to Scalatra 2.3 and Slick 2.1.
Some compilation errors in Slick code are remaining.
2014-07-30 07:36:35 +09:00
Tomofumi Tanaka
d2317d0a97 (refs #437)Fix typo
Threre is more good function name, but i have no idea 😵
2014-07-29 01:24:09 +09:00
Tomofumi Tanaka
972628eb65 (refs #437)Show author and committer at files view 2014-07-29 01:11:27 +09:00
Tomofumi Tanaka
51a56356cb (refs #437)Show author at repo file list view 2014-07-29 00:56:34 +09:00
Tomofumi Tanaka
2bb1f6168a (refs #437)Show author instead of committer
* Explicit classify committer and author
* Use author to render avatar image html
* Support commit view
2014-07-29 00:06:45 +09:00
shimamoto
b13820fc0e Improved model package. The details are as follows:
* Fix the Profiles class from package object to simple object
* Fix the row case class to model package
* Define the alias of JdbcBackend#Session
2014-07-28 04:52:56 +09:00
takezoe
723de9e81e Fix TestCase 2014-07-27 22:56:02 +09:00
takezoe
3e161353ed Merge branch 'slick2-compilation-problem' 2014-07-27 21:24:40 +09:00
takezoe
2a8706630a (refs #445)Fix yen to backslash 2014-07-27 17:11:27 +09:00
Naoki Takezoe
121b6ee641 Fix incremental compilation problem caused by Slick.
This is temporary fix to decrease compilation time in development. Therefore this fix will be reverted in the future to add multiple database support capability.
2014-07-27 03:31:45 +09:00
Naoki Takezoe
34e299bf52 (refs #445)Keep line separator in online file editing 2014-07-26 23:15:47 +09:00
Naoki Takezoe
0822b7b5f3 (refs #444)Fix pull request count in dashboard 2014-07-26 19:12:09 +09:00
Naoki Takezoe
618110327a (refs #443)Fix merge guidance 2014-07-26 04:00:15 +09:00
Naoki Takezoe
f58f476060 (refs #441)Upgrade h2 to the latest version. 2014-07-25 15:46:29 +09:00
Naoki Takezoe
f5a544603a Experimental JDBC API for JavaScript plug-ins 2014-07-24 01:39:26 +09:00
Naoki Takezoe
89515cd087 Add an argument RepositoryInfo to RepositoryAction 2014-07-24 00:57:21 +09:00
Naoki Takezoe
37731c4163 Enable plugin system 2014-07-23 19:50:38 +09:00
Naoki Takezoe
1d4720d784 Merge pull request #439 from jmu/master
Fix IE copy not working
2014-07-23 01:30:21 +09:00
jmu
a10b053489 fix ie copy not working 2014-07-22 19:50:02 +08:00
takezoe
6122c8a1e1 Fix #438 2014-07-21 03:31:52 +09:00
Tomofumi Tanaka
fa9254c240 (refs #435)Correct merge commit message
Use ${user}/${branch} instead of ${user}/{repoName}
2014-07-16 23:33:14 +09:00
Tomofumi Tanaka
10616bca7d (refs #436)Encode branch name to delete at pullreq view 2014-07-16 19:24:25 +09:00
Naoki Takezoe
307f7e15e9 (refs #431)Fix CSS layout in Wiki page 2014-07-15 07:07:52 +09:00
Naoki Takezoe
86cf97d76b Fix TODO: Close issue and send webhook from online editing 2014-07-14 01:10:33 +09:00
shimamoto
01f6590c04 Fix TODO. 2014-07-14 00:08:34 +09:00
shimamoto
8f0c22bae9 Improve slick session(because transaction for unnecessary). 2014-07-13 23:54:53 +09:00
Naoki Takezoe
652a68c5b1 Fix TODO 2014-07-13 23:20:16 +09:00
Naoki Takezoe
1f56e1360d Fix font size 2014-07-13 16:31:24 +09:00
Naoki Takezoe
38475ffefe Fix avatar image size 2014-07-13 16:26:57 +09:00
Naoki Takezoe
7a44a4d726 Merge branch '401-news-feed-of-private-repo' of https://github.com/utensil/gitbucket into utensil-401-news-feed-of-private-repo
Conflicts:
	src/main/scala/app/IndexController.scala
	src/main/scala/service/ActivityService.scala
2014-07-13 16:07:42 +09:00
Naoki Takezoe
9dbc0c3fd6 Change LDAPUtil method name 2014-07-13 15:53:12 +09:00
Naoki Takezoe
56bb43ea6b AccountUtil is merged to LDAPUtil 2014-07-13 15:13:59 +09:00
Naoki Takezoe
b287c1f60d Merge branch 'add-features-to-ldapauth' of https://github.com/yjkony/gitbucket into yjkony-add-features-to-ldapauth
Conflicts:
	src/main/scala/app/IndexController.scala
	src/main/scala/service/SystemSettingsService.scala
	src/main/scala/util/LDAPUtil.scala
	src/main/scala/util/Notifier.scala
2014-07-13 13:49:04 +09:00
Naoki Takezoe
258d53b7a6 Disable submit buttons while performing validation 2014-07-13 03:48:44 +09:00
Naoki Takezoe
2e11d6dd78 Disable submit buttons while performing validation 2014-07-13 03:47:40 +09:00
Tomofumi Tanaka
a2a2e22485 Fix compilation error service test case 2014-07-09 00:25:40 +09:00
Tomofumi Tanaka
c182cde14b Revert "Disable TestCase for Services"
This reverts commit 104c3bc89d.
2014-07-08 23:55:15 +09:00
utensil
3683a5fb7d Show newsfeed of private repo to members of owner group 2014-06-22 09:59:59 +00:00
utensil
1223bf2fd8 Show newsfeed of private repo to its owner 2014-06-22 08:10:37 +00:00
yjkony
e2c99a46be Merge commit Tag 2.0 'db5395ddbc4aef485415408720dd09cfc215b527' into add-features-to-ldapauth
Conflicts:
	src/main/twirl/account/edit.scala.html
2014-06-02 17:01:22 +09:00
yjkony
8c35310cd6 Merge commit Tag 1.13 ('3e82534c78a72e17dd3b79e091521d75cb4d3855') into add-features-to-ldapauth
Conflicts:
	src/main/scala/service/AccountService.scala
	src/main/scala/util/LDAPUtil.scala
2014-05-01 11:56:56 +09:00
yjkony
00af52815d Merge commit '5317ac5e031a29438657952fb882532af296135b' (tag 1.12) into add-features-to-ldapauth 2014-03-31 12:37:23 +09:00
yjkony
9175cf5c71 Merge branch 'master' into add-features-to-ldapauth
Conflicts:
	src/main/twirl/account/edit.scala.html
2014-03-14 15:56:08 +09:00
yjkony
a74bbd3eeb Merge branch 'master' into add-features-to-ldapauth 2014-03-13 18:10:41 +09:00
yjkony
4e2a3fdbd0 Change trigger of "Disalbe mail resolve is enalbed" from "When system settings check-box is ON" to "When mail attribute is empty". 2014-03-11 12:56:00 +09:00
yjkony
8d200c72d3 Merge branch 'master' into add-features-to-ldapauth 2014-03-10 11:05:02 +09:00
yjkony
18cd967a9c Modify wrong label of "Additional filter condition" label in system settings page 2014-03-06 11:03:04 +09:00
yjkony
328d6c1d17 Merge branch 'master' into add-features-to-ldapauth 2014-03-06 10:52:16 +09:00
yjkony
a335c31385 Revert unnecessary format changes. 2014-03-04 10:54:54 +09:00
yjkony
97349a9bb2 Merge branch 'master' into add-features-to-ldapauth 2014-03-04 10:16:24 +09:00
yjkony
ce3b6ed7c2 Revert line separator from LF to CRLF 2014-03-04 10:16:12 +09:00
yjkony
5e0619b500 Sync upstream/maste to master and Merge branch 'master' into add-features-to-ldapauth
Conflicts:
	src/main/scala/app/IndexController.scala
	src/main/scala/service/SystemSettingsService.scala
	src/main/twirl/admin/system.scala.html
2014-03-03 15:46:38 +09:00
yjkony
639e7e0b3f Add features (additional filter condition / disable mail resolve) to LDAP authentication. 2014-02-28 21:32:42 +09:00
83 changed files with 914 additions and 640 deletions

View File

@@ -80,6 +80,14 @@ Run the following commands in `Terminal` to
Release Notes Release Notes
-------- --------
### 2.2 - 4 Aug 2014
- Plug-in system is available
- Move to Scala 2.11, Scalatra 2.3 and Slick 2.1
- tar.gz export for repository contents
- LDAP authentication improvement (mail address became optional)
- Show news feed of a private repository to members
- Some bug fix and improvements
### 2.1 - 6 Jul 2014 ### 2.1 - 6 Jul 2014
- Upgrade to Slick 2.0 from 1.9 - Upgrade to Slick 2.0 from 1.9
- Base part of the plug-in system is merged - Base part of the plug-in system is merged

View File

@@ -4,7 +4,7 @@
<property name="target.dir" value="target"/> <property name="target.dir" value="target"/>
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/> <property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
<property name="jetty.dir" value="embed-jetty"/> <property name="jetty.dir" value="embed-jetty"/>
<property name="scala.version" value="2.10"/> <property name="scala.version" value="2.11"/>
<property name="gitbucket.version" value="0.0.1"/> <property name="gitbucket.version" value="0.0.1"/>
<property name="jetty.version" value="8.1.8.v20121106"/> <property name="jetty.version" value="8.1.8.v20121106"/>
<property name="servlet.version" value="3.0.0.v201112011016"/> <property name="servlet.version" value="3.0.0.v201112011016"/>

View File

@@ -8,8 +8,8 @@ object MyBuild extends Build {
val Organization = "jp.sf.amateras" val Organization = "jp.sf.amateras"
val Name = "gitbucket" val Name = "gitbucket"
val Version = "0.0.1" val Version = "0.0.1"
val ScalaVersion = "2.10.3" val ScalaVersion = "2.11.2"
val ScalatraVersion = "2.2.1" val ScalatraVersion = "2.3.0"
lazy val project = Project ( lazy val project = Project (
"gitbucket", "gitbucket",
@@ -26,23 +26,24 @@ object MyBuild extends Build {
), ),
scalacOptions := Seq("-deprecation", "-language:postfixOps"), scalacOptions := Seq("-deprecation", "-language:postfixOps"),
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.0.0.201306101825-r", "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.1.201406201815-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.1.201406201815-r",
"org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test", "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
"org.scalatra" %% "scalatra-json" % ScalatraVersion, "org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.2.5", "org.json4s" %% "json4s-jackson" % "3.2.10",
"jp.sf.amateras" %% "scalatra-forms" % "0.0.14", "jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
"commons-io" % "commons-io" % "2.4", "commons-io" % "commons-io" % "2.4",
"org.pegdown" % "pegdown" % "1.4.1", "org.pegdown" % "pegdown" % "1.4.1",
"org.apache.commons" % "commons-compress" % "1.5", "org.apache.commons" % "commons-compress" % "1.5",
"org.apache.commons" % "commons-email" % "1.3.1", "org.apache.commons" % "commons-email" % "1.3.1",
"org.apache.httpcomponents" % "httpclient" % "4.3", "org.apache.httpcomponents" % "httpclient" % "4.3",
"org.apache.sshd" % "apache-sshd" % "0.11.0", "org.apache.sshd" % "apache-sshd" % "0.11.0",
"com.typesafe.slick" %% "slick" % "2.0.2", "com.typesafe.slick" %% "slick" % "2.1.0-RC3",
"org.mozilla" % "rhino" % "1.7R4", "org.mozilla" % "rhino" % "1.7R4",
"com.novell.ldap" % "jldap" % "2009-10-07", "com.novell.ldap" % "jldap" % "2009-10-07",
"org.quartz-scheduler" % "quartz" % "2.2.1", "org.quartz-scheduler" % "quartz" % "2.2.1",
"com.h2database" % "h2" % "1.3.173", "com.h2database" % "h2" % "1.4.180",
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime", "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided", "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"), "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),

View File

@@ -24,8 +24,9 @@ abstract class ControllerBase extends ScalatraFilter
implicit val jsonFormats = DefaultFormats implicit val jsonFormats = DefaultFormats
// Don't set content type via Accept header. // TODO Scala 2.11
override def format(implicit request: HttpServletRequest) = "" // // Don't set content type via Accept header.
// override def format(implicit request: HttpServletRequest) = ""
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try { override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
val httpRequest = request.asInstanceOf[HttpServletRequest] val httpRequest = request.asInstanceOf[HttpServletRequest]
@@ -125,11 +126,13 @@ abstract class ControllerBase extends ScalatraFilter
} }
} }
override def fullUrl(path: String, params: Iterable[(String, Any)] = Iterable.empty, // TODO Scala 2.11
includeContextPath: Boolean = true, includeServletPath: Boolean = true) override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
(implicit request: HttpServletRequest, response: HttpServletResponse) = includeContextPath: Boolean = true, includeServletPath: Boolean = true,
absolutize: Boolean = true, withSessionId: Boolean = true)
(implicit request: HttpServletRequest, response: HttpServletResponse): String =
if (path.startsWith("http")) path if (path.startsWith("http")) path
else baseUrl + url(path, params, false, false, false) else baseUrl + super.url(path, params, false, false, false)
} }

View File

@@ -49,21 +49,21 @@ trait DashboardControllerBase extends ControllerBase {
) )
val userName = context.loginAccount.get.userName val userName = context.loginAccount.get.userName
val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name) val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
val filterUser = Map(filter -> userName) val filterUser = Map(filter -> userName)
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
//
dashboard.html.issues( dashboard.html.issues(
issues.html.listparts( issues.html.listparts(
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, repositories: _*), searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
page, page,
countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*), countIssue(condition.copy(state = "open" ), filterUser, false, userRepos: _*),
countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*), countIssue(condition.copy(state = "closed"), filterUser, false, userRepos: _*),
condition), condition),
countIssue(condition, Map.empty, false, repositories: _*), countIssue(condition, Map.empty, false, userRepos: _*),
countIssue(condition, Map("assigned" -> userName), false, repositories: _*), countIssue(condition, Map("assigned" -> userName), false, userRepos: _*),
countIssue(condition, Map("created_by" -> userName), false, repositories: _*), countIssue(condition, Map("created_by" -> userName), false, userRepos: _*),
countIssueGroupByRepository(condition, filterUser, false, repositories: _*), countIssueGroupByRepository(condition, filterUser, false, userRepos: _*),
condition, condition,
filter) filter)
@@ -80,25 +80,26 @@ trait DashboardControllerBase extends ControllerBase {
}.copy(repo = repository)) }.copy(repo = repository))
val userName = context.loginAccount.get.userName val userName = context.loginAccount.get.userName
val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name) val allRepos = getAllRepositories()
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
val filterUser = Map(filter -> userName) val filterUser = Map(filter -> userName)
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
val counts = countIssueGroupByRepository( val counts = countIssueGroupByRepository(
IssueSearchCondition().copy(state = condition.state), Map.empty, true, repositories: _*) IssueSearchCondition().copy(state = condition.state), Map.empty, true, userRepos: _*)
dashboard.html.pulls( dashboard.html.pulls(
pulls.html.listparts( pulls.html.listparts(
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, repositories: _*), searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
page, page,
countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*), countIssue(condition.copy(state = "open" ), filterUser, true, allRepos: _*),
countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*), countIssue(condition.copy(state = "closed"), filterUser, true, allRepos: _*),
condition, condition,
None, None,
false), false),
getPullRequestCountGroupByUser(condition.state == "closed", None, None), getPullRequestCountGroupByUser(condition.state == "closed", None, None),
getRepositoryNamesOfUser(userName).map { RepoName => userRepos.map { case (userName, repoName) =>
(userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0)) (userName, repoName, counts.find { x => x._1 == userName && x._2 == repoName }.map(_._3).getOrElse(0))
}.sortBy(_._3).reverse, }.sortBy(_._3).reverse,
condition, condition,
filter) filter)

View File

@@ -20,11 +20,23 @@ trait IndexControllerBase extends ControllerBase {
get("/"){ get("/"){
val loginAccount = context.loginAccount val loginAccount = context.loginAccount
if(loginAccount.isEmpty) {
html.index(getRecentActivities(), html.index(getRecentActivities(),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true), getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil) loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
) )
} else {
val loginUserName = loginAccount.get.userName
val loginUserGroups = getGroupsByUserName(loginUserName)
var visibleOwnerSet : Set[String] = Set(loginUserName)
visibleOwnerSet ++= loginUserGroups
html.index(getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
)
}
} }
get("/signin"){ get("/signin"){
@@ -59,6 +71,10 @@ trait IndexControllerBase extends ControllerBase {
session.setAttribute(Keys.Session.LoginAccount, account) session.setAttribute(Keys.Session.LoginAccount, account)
updateLastLoginDate(account.userName) updateLastLoginDate(account.userName)
if(LDAPUtil.isDummyMailAddress(account)) {
redirect("/" + account.userName + "/_edit")
}
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl => flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
if(redirectUrl.stripSuffix("/") == request.getContextPath){ if(redirectUrl.stripSuffix("/") == request.getContextPath){
redirect("/") redirect("/")
@@ -72,8 +88,6 @@ trait IndexControllerBase extends ControllerBase {
/** /**
* JSON API for collaborator completion. * JSON API for collaborator completion.
*
* TODO Move to other controller?
*/ */
get("/_user/proposals")(usersOnly { get("/_user/proposals")(usersOnly {
contentType = formats("json") contentType = formats("json")
@@ -82,5 +96,11 @@ trait IndexControllerBase extends ControllerBase {
) )
}) })
/**
* JSON APU for checking user existence.
*/
post("/_user/existence")(usersOnly {
getAccountByUserName(params("userName")).isDefined
})
} }

View File

@@ -156,7 +156,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
mergeCommit.setAuthor(personIdent) mergeCommit.setAuthor(personIdent)
mergeCommit.setCommitter(personIdent) mergeCommit.setCommitter(personIdent)
mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n\n" + mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" +
form.message) form.message)
// insertObject and got mergeCommit Object Id // insertObject and got mergeCommit Object Id
@@ -443,7 +443,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit => val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
new CommitInfo(revCommit) new CommitInfo(revCommit)
}.toList.splitWith { (commit1, commit2) => }.toList.splitWith { (commit1, commit2) =>
view.helpers.date(commit1.time) == view.helpers.date(commit2.time) view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
} }
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true) val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)

View File

@@ -8,23 +8,31 @@ import _root_.util._
import service._ import service._
import org.scalatra._ import org.scalatra._
import java.io.File import java.io.File
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.{ArchiveCommand, Git}
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
import org.eclipse.jgit.lib._ import org.eclipse.jgit.lib._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.eclipse.jgit.treewalk._ import org.eclipse.jgit.treewalk._
import java.util.zip.{ZipEntry, ZipOutputStream}
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.dircache.DirCache import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk} import org.eclipse.jgit.revwalk.RevCommit
import service.WebHookService.WebHookPayload
class RepositoryViewerController extends RepositoryViewerControllerBase class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
with ReferrerAuthenticator with CollaboratorsAuthenticator
/** /**
* The repository viewer. * The repository viewer.
*/ */
trait RepositoryViewerControllerBase extends ControllerBase { trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator => self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
ArchiveCommand.registerFormat("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
case class EditorForm( case class EditorForm(
branch: String, branch: String,
@@ -32,6 +40,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
content: String, content: String,
message: Option[String], message: Option[String],
charset: String, charset: String,
lineSeparator: String,
newFileName: String, newFileName: String,
oldFileName: Option[String] oldFileName: Option[String]
) )
@@ -49,6 +58,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"content" -> trim(label("Content", text(required))), "content" -> trim(label("Content", text(required))),
"message" -> trim(label("Message", optional(text()))), "message" -> trim(label("Message", optional(text()))),
"charset" -> trim(label("Charset", text(required))), "charset" -> trim(label("Charset", text(required))),
"lineSeparator" -> trim(label("Line Separator", text(required))),
"newFileName" -> trim(label("Filename", text(required))), "newFileName" -> trim(label("Filename", text(required))),
"oldFileName" -> trim(label("Old filename", optional(text()))) "oldFileName" -> trim(label("Old filename", optional(text())))
)(EditorForm.apply) )(EditorForm.apply)
@@ -101,7 +111,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
case Right((logs, hasNext)) => case Right((logs, hasNext)) =>
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository, repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) => logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.time) == view.helpers.date(commit2.time) view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}, page, hasNext) }, page, hasNext)
case Left(_) => NotFound case Left(_) => NotFound
} }
@@ -142,7 +152,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) => post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
commitFile(repository, form.branch, form.path, Some(form.newFileName), None, form.content, form.charset, commitFile(repository, form.branch, form.path, Some(form.newFileName), None,
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
form.message.getOrElse(s"Create ${form.newFileName}")) form.message.getOrElse(s"Create ${form.newFileName}"))
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
@@ -151,7 +162,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) => post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName, form.content, form.charset, commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName,
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
if(form.oldFileName.exists(_ == form.newFileName)){ if(form.oldFileName.exists(_ == form.newFileName)){
form.message.getOrElse(s"Update ${form.newFileName}") form.message.getOrElse(s"Update ${form.newFileName}")
} else { } else {
@@ -252,50 +264,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Download repository contents as an archive. * Download repository contents as an archive.
*/ */
get("/:owner/:repository/archive/*")(referrersOnly { repository => get("/:owner/:repository/archive/*")(referrersOnly { repository =>
val name = multiParams("splat").head multiParams("splat").head match {
case name if name.endsWith(".zip") =>
if(name.endsWith(".zip")){ archiveRepository(name, ".zip", repository)
val revision = name.stripSuffix(".zip") case name if name.endsWith(".tar.gz") =>
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId) archiveRepository(name, ".tar.gz", repository)
if(workDir.exists){ case _ => BadRequest
FileUtils.deleteDirectory(workDir)
}
workDir.mkdirs
val zipFile = new File(workDir, repository.name + "-" +
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + ".zip")
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
using(new TreeWalk(git.getRepository)){ walk =>
val reader = walk.getObjectReader
val objectId = new MutableObjectId
using(new ZipOutputStream(new java.io.FileOutputStream(zipFile))){ out =>
walk.addTree(revCommit.getTree)
walk.setRecursive(true)
while(walk.next){
val name = walk.getPathString
val mode = walk.getFileMode(0)
if(mode == FileMode.REGULAR_FILE || mode == FileMode.EXECUTABLE_FILE){
walk.getObjectId(objectId, 0)
val entry = new ZipEntry(name)
val loader = reader.open(objectId)
entry.setSize(loader.getSize)
out.putNextEntry(entry)
loader.copyTo(out)
}
}
}
}
}
contentType = "application/octet-stream"
response.setHeader("Content-Disposition", s"attachment; filename=${zipFile.getName}")
zipFile
} else {
BadRequest
} }
}) })
@@ -408,8 +382,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)))) List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
// TODO invoke hook // close issue by commit message
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
// call web hook
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
getWebHookURLs(repository.owner, repository.name) match {
case webHookURLs if(webHookURLs.nonEmpty) =>
for(ownerAccount <- getAccountByUserName(repository.owner)){
callWebHook(repository.owner, repository.name, webHookURLs,
WebHookPayload(git, loginAccount, headName, repository, List(commit), ownerAccount))
}
case _ =>
}
} }
} }
} }
@@ -429,4 +414,29 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
} }
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): File = {
val revision = name.stripSuffix(suffix)
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
if(workDir.exists) {
FileUtils.deleteDirectory(workDir)
}
workDir.mkdirs
val file = new File(workDir, repository.name + "-" +
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
using(new java.io.FileOutputStream(file)) { out =>
git.archive
.setFormat(suffix.tail)
.setTree(revCommit.getTree)
.setOutputStream(out)
.call()
}
contentType = "application/octet-stream"
response.setHeader("Content-Disposition", s"attachment; filename=${file.getName}")
file
}
}
} }

View File

@@ -10,6 +10,7 @@ import ssh.SshServer
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import java.io.FileInputStream import java.io.FileInputStream
import plugin.{Plugin, PluginSystem} import plugin.{Plugin, PluginSystem}
import org.scalatra.Ok
class SystemSettingsController extends SystemSettingsControllerBase class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with AdminAuthenticator with AccountService with AdminAuthenticator
@@ -41,8 +42,9 @@ trait SystemSettingsControllerBase extends ControllerBase {
"bindPassword" -> trim(label("Bind Password", optional(text()))), "bindPassword" -> trim(label("Bind Password", optional(text()))),
"baseDN" -> trim(label("Base DN", text(required))), "baseDN" -> trim(label("Base DN", text(required))),
"userNameAttribute" -> trim(label("User name attribute", text(required))), "userNameAttribute" -> trim(label("User name attribute", text(required))),
"additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))),
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))), "fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
"mailAttribute" -> trim(label("Mail address attribute", text(required))), "mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
"tls" -> trim(label("Enable TLS", optional(boolean()))), "tls" -> trim(label("Enable TLS", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text()))) "keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply)) )(Ldap.apply))
@@ -81,34 +83,33 @@ trait SystemSettingsControllerBase extends ControllerBase {
redirect("/admin/system") redirect("/admin/system")
}) })
// TODO Enable commented code to enable plug-in system get("/admin/plugins")(adminOnly {
// get("/admin/plugins")(adminOnly { val installedPlugins = plugin.PluginSystem.plugins
// val installedPlugins = plugin.PluginSystem.plugins val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
// val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable") admin.plugins.html.installed(installedPlugins, updatablePlugins)
// admin.plugins.html.installed(installedPlugins, updatablePlugins) })
// })
// post("/admin/plugins/_update", pluginForm)(adminOnly { form =>
// post("/admin/plugins/_update", pluginForm)(adminOnly { form => deletePlugins(form.pluginIds)
// deletePlugins(form.pluginIds) installPlugins(form.pluginIds)
// installPlugins(form.pluginIds) redirect("/admin/plugins")
// redirect("/admin/plugins") })
// })
// post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
// post("/admin/plugins/_delete", pluginForm)(adminOnly { form => deletePlugins(form.pluginIds)
// deletePlugins(form.pluginIds) redirect("/admin/plugins")
// redirect("/admin/plugins") })
// })
// get("/admin/plugins/available")(adminOnly {
// get("/admin/plugins/available")(adminOnly { val installedPlugins = plugin.PluginSystem.plugins
// val installedPlugins = plugin.PluginSystem.plugins val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available")
// val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available") admin.plugins.html.available(availablePlugins)
// admin.plugins.html.available(availablePlugins) })
// })
// post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
// post("/admin/plugins/_install", pluginForm)(adminOnly { form => installPlugins(form.pluginIds)
// installPlugins(form.pluginIds) redirect("/admin/plugins")
// redirect("/admin/plugins") })
// })
// get("/admin/plugins/console")(adminOnly { // get("/admin/plugins/console")(adminOnly {
// admin.plugins.html.console() // admin.plugins.html.console()

View File

@@ -182,11 +182,6 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
} }
}) })
// TODO Move to other generic controller?
post("/admin/users/_usercheck"){
getAccountByUserName(params("userName")).isDefined
}
private def members: Constraint = new Constraint(){ private def members: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(name: String, value: String, messages: Messages): Option[String] = {
if(value.split(",").exists { if(value.split(",").exists {

View File

@@ -21,9 +21,9 @@ trait AccountComponent { self: Profile =>
val removed = column[Boolean]("REMOVED") val removed = column[Boolean]("REMOVED")
def * = (userName, fullName, mailAddress, password, isAdmin, 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)
} }
}
case class Account( case class Account(
userName: String, userName: String,
fullName: String, fullName: String,
mailAddress: String, mailAddress: String,
@@ -36,5 +36,4 @@ trait AccountComponent { self: Profile =>
image: Option[String], image: Option[String],
isGroupAccount: Boolean, isGroupAccount: Boolean,
isRemoved: Boolean isRemoved: Boolean
) )
}

View File

@@ -15,8 +15,9 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
val activityDate = column[java.util.Date]("ACTIVITY_DATE") val activityDate = column[java.util.Date]("ACTIVITY_DATE")
def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply) def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
} }
}
case class Activity( case class Activity(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
activityUserName: String, activityUserName: String,
@@ -25,5 +26,4 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
additionalInfo: Option[String], additionalInfo: Option[String],
activityDate: java.util.Date, activityDate: java.util.Date,
activityId: Int = 0 activityId: Int = 0
) )
}

View File

@@ -8,40 +8,40 @@ protected[model] trait TemplateComponent { self: Profile =>
val repositoryName = column[String]("REPOSITORY_NAME") val repositoryName = column[String]("REPOSITORY_NAME")
def byRepository(owner: String, repository: String) = def byRepository(owner: String, repository: String) =
(userName is owner.bind) && (repositoryName is repository.bind) (userName === owner.bind) && (repositoryName === repository.bind)
def byRepository(userName: Column[String], repositoryName: Column[String]) = def byRepository(userName: Column[String], repositoryName: Column[String]) =
(this.userName is userName) && (this.repositoryName is repositoryName) (this.userName === userName) && (this.repositoryName === repositoryName)
} }
trait IssueTemplate extends BasicTemplate { self: Table[_] => trait IssueTemplate extends BasicTemplate { self: Table[_] =>
val issueId = column[Int]("ISSUE_ID") val issueId = column[Int]("ISSUE_ID")
def byIssue(owner: String, repository: String, issueId: Int) = def byIssue(owner: String, repository: String, issueId: Int) =
byRepository(owner, repository) && (this.issueId is issueId.bind) byRepository(owner, repository) && (this.issueId === issueId.bind)
def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
byRepository(userName, repositoryName) && (this.issueId is issueId) byRepository(userName, repositoryName) && (this.issueId === issueId)
} }
trait LabelTemplate extends BasicTemplate { self: Table[_] => trait LabelTemplate extends BasicTemplate { self: Table[_] =>
val labelId = column[Int]("LABEL_ID") val labelId = column[Int]("LABEL_ID")
def byLabel(owner: String, repository: String, labelId: Int) = def byLabel(owner: String, repository: String, labelId: Int) =
byRepository(owner, repository) && (this.labelId is labelId.bind) byRepository(owner, repository) && (this.labelId === labelId.bind)
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
byRepository(userName, repositoryName) && (this.labelId is labelId) byRepository(userName, repositoryName) && (this.labelId === labelId)
} }
trait MilestoneTemplate extends BasicTemplate { self: Table[_] => trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
val milestoneId = column[Int]("MILESTONE_ID") val milestoneId = column[Int]("MILESTONE_ID")
def byMilestone(owner: String, repository: String, milestoneId: Int) = def byMilestone(owner: String, repository: String, milestoneId: Int) =
byRepository(owner, repository) && (this.milestoneId is milestoneId.bind) byRepository(owner, repository) && (this.milestoneId === milestoneId.bind)
def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
byRepository(userName, repositoryName) && (this.milestoneId is milestoneId) byRepository(userName, repositoryName) && (this.milestoneId === milestoneId)
} }
} }

View File

@@ -10,12 +10,12 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply) def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply)
def byPrimaryKey(owner: String, repository: String, collaborator: String) = def byPrimaryKey(owner: String, repository: String, collaborator: String) =
byRepository(owner, repository) && (collaboratorName is collaborator.bind) byRepository(owner, repository) && (collaboratorName === collaborator.bind)
} }
}
case class Collaborator( case class Collaborator(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
collaboratorName: String collaboratorName: String
) )
}

View File

@@ -11,10 +11,10 @@ trait GroupMemberComponent { self: Profile =>
val isManager = column[Boolean]("MANAGER") val isManager = column[Boolean]("MANAGER")
def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply) def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply)
} }
}
case class GroupMember( case class GroupMember(
groupName: String, groupName: String,
userName: String, userName: String,
isManager: Boolean isManager: Boolean
) )
}

View File

@@ -31,8 +31,9 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId) def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
} }
}
case class Issue( case class Issue(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
issueId: Int, issueId: Int,
@@ -44,5 +45,5 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
closed: Boolean, closed: Boolean,
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date, updatedDate: java.util.Date,
isPullRequest: Boolean) isPullRequest: Boolean
} )

View File

@@ -17,10 +17,11 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply) def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
} }
}
case class IssueComment( case class IssueComment(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
issueId: Int, issueId: Int,
@@ -30,5 +31,4 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
content: String, content: String,
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date updatedDate: java.util.Date
) )
}

View File

@@ -8,12 +8,13 @@ trait IssueLabelComponent extends TemplateComponent { self: Profile =>
class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate { class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate {
def * = (userName, repositoryName, issueId, labelId) <> (IssueLabel.tupled, IssueLabel.unapply) def * = (userName, repositoryName, issueId, labelId) <> (IssueLabel.tupled, IssueLabel.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) = def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) =
byIssue(owner, repository, issueId) && (this.labelId is labelId.bind) byIssue(owner, repository, issueId) && (this.labelId === labelId.bind)
} }
}
case class IssueLabel( case class IssueLabel(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
issueId: Int, issueId: Int,
labelId: Int) labelId: Int
} )

View File

@@ -14,8 +14,9 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId) def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = byLabel(userName, repositoryName, labelId) def byPrimaryKey(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = byLabel(userName, repositoryName, labelId)
} }
}
case class Label( case class Label(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
labelId: Int = 0, labelId: Int = 0,
@@ -33,5 +34,4 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
"FFFFFF" "FFFFFF"
} }
} }
}
} }

View File

@@ -17,13 +17,14 @@ trait MilestoneComponent extends TemplateComponent { self: Profile =>
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId) def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = byMilestone(userName, repositoryName, milestoneId) def byPrimaryKey(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = byMilestone(userName, repositoryName, milestoneId)
} }
}
case class Milestone( case class Milestone(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
milestoneId: Int = 0, milestoneId: Int = 0,
title: String, title: String,
description: Option[String], description: Option[String],
dueDate: Option[java.util.Date], dueDate: Option[java.util.Date],
closedDate: Option[java.util.Date]) closedDate: Option[java.util.Date]
} )

View File

@@ -1,9 +1,7 @@
package model package model
import slick.driver.JdbcProfile
trait Profile { trait Profile {
val profile: JdbcProfile val profile: slick.driver.JdbcProfile
import profile.simple._ import profile.simple._
// java.util.Date Mapped Column Types // java.util.Date Mapped Column Types
@@ -17,3 +15,27 @@ trait Profile {
} }
} }
object Profile extends {
val profile = slick.driver.H2Driver
} with AccountComponent
with ActivityComponent
with CollaboratorComponent
with GroupMemberComponent
with IssueComponent
with IssueCommentComponent
with IssueLabelComponent
with LabelComponent
with MilestoneComponent
with PullRequestComponent
with RepositoryComponent
with SshKeyComponent
with WebHookComponent with Profile {
/**
* Returns system date.
*/
def currentDate = new java.util.Date()
}

View File

@@ -17,8 +17,9 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = byIssue(userName, repositoryName, issueId) def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = byIssue(userName, repositoryName, issueId)
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = byIssue(userName, repositoryName, issueId) def byPrimaryKey(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = byIssue(userName, repositoryName, issueId)
} }
}
case class PullRequest( case class PullRequest(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
issueId: Int, issueId: Int,
@@ -28,5 +29,4 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
requestBranch: String, requestBranch: String,
commitIdFrom: String, commitIdFrom: String,
commitIdTo: String commitIdTo: String
) )
}

View File

@@ -21,8 +21,9 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
} }
}
case class Repository( case class Repository(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
isPrivate: Boolean, isPrivate: Boolean,
@@ -35,5 +36,4 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
originRepositoryName: Option[String], originRepositoryName: Option[String],
parentUserName: Option[String], parentUserName: Option[String],
parentRepositoryName: Option[String] parentRepositoryName: Option[String]
) )
}

View File

@@ -12,13 +12,13 @@ trait SshKeyComponent { self: Profile =>
val publicKey = column[String]("PUBLIC_KEY") val publicKey = column[String]("PUBLIC_KEY")
def * = (userName, sshKeyId, title, publicKey) <> (SshKey.tupled, SshKey.unapply) def * = (userName, sshKeyId, title, publicKey) <> (SshKey.tupled, SshKey.unapply)
def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName is userName.bind) && (this.sshKeyId is sshKeyId.bind) def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)
} }
}
case class SshKey( case class SshKey(
userName: String, userName: String,
sshKeyId: Int = 0, sshKeyId: Int = 0,
title: String, title: String,
publicKey: String publicKey: String
) )
}

View File

@@ -9,12 +9,12 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
val url = column[String]("URL") val url = column[String]("URL")
def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply) def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply)
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url is url.bind) def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
} }
}
case class WebHook( case class WebHook(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
url: String url: String
) )
}

View File

@@ -1,24 +1,3 @@
package object model extends { package object model {
// TODO [Slick 2.0]Should be configurable? type Session = slick.jdbc.JdbcBackend#Session
val profile = slick.driver.H2Driver
// TODO [Slick 2.0]To avoid compilation error about delete invocation. Why can't this error be resolved by import profile.simple._?
val simple = profile.simple
} with AccountComponent
with ActivityComponent
with CollaboratorComponent
with GroupMemberComponent
with IssueComponent
with IssueCommentComponent
with IssueLabelComponent
with LabelComponent
with MilestoneComponent
with PullRequestComponent
with RepositoryComponent
with SshKeyComponent
with WebHookComponent with Profile {
/**
* Returns system date.
*/
def currentDate = new java.util.Date()
} }

View File

@@ -3,19 +3,24 @@ package plugin
import org.mozilla.javascript.{Context => JsContext} import org.mozilla.javascript.{Context => JsContext}
import org.mozilla.javascript.{Function => JsFunction} import org.mozilla.javascript.{Function => JsFunction}
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import plugin.PluginSystem.{Action, GlobalMenu, RepositoryMenu} import plugin.PluginSystem._
import util.ControlUtil._
import plugin.PluginSystem.GlobalMenu
import plugin.PluginSystem.RepositoryAction
import plugin.PluginSystem.Action
import plugin.PluginSystem.RepositoryMenu
class JavaScriptPlugin(val id: String, val version: String, class JavaScriptPlugin(val id: String, val version: String,
val author: String, val url: String, val description: String) extends Plugin { val author: String, val url: String, val description: String) extends Plugin {
private val repositoryMenuList = ListBuffer[RepositoryMenu]() private val repositoryMenuList = ListBuffer[RepositoryMenu]()
private val globalMenuList = ListBuffer[GlobalMenu]() private val globalMenuList = ListBuffer[GlobalMenu]()
private val repositoryActionList = ListBuffer[Action]() private val repositoryActionList = ListBuffer[RepositoryAction]()
private val globalActionList = ListBuffer[Action]() private val globalActionList = ListBuffer[Action]()
def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList
def globalMenus : List[GlobalMenu] = globalMenuList.toList def globalMenus : List[GlobalMenu] = globalMenuList.toList
def repositoryActions : List[Action] = repositoryActionList.toList def repositoryActions : List[RepositoryAction] = repositoryActionList.toList
def globalActions : List[Action] = globalActionList.toList def globalActions : List[Action] = globalActionList.toList
def addRepositoryMenu(label: String, name: String, url: String, icon: String, condition: JsFunction): Unit = { def addRepositoryMenu(label: String, name: String, url: String, icon: String, condition: JsFunction): Unit = {
@@ -52,16 +57,40 @@ class JavaScriptPlugin(val id: String, val version: String,
} }
def addRepositoryAction(path: String, function: JsFunction): Unit = { def addRepositoryAction(path: String, function: JsFunction): Unit = {
repositoryActionList += Action(path, (request, response) => { repositoryActionList += RepositoryAction(path, (request, response, repository) => {
val context = JsContext.enter() val context = JsContext.enter()
try { try {
function.call(context, function, function, Array(request, response)) function.call(context, function, function, Array(request, response, repository))
} finally { } finally {
JsContext.exit() JsContext.exit()
} }
}) })
} }
object db {
// TODO Use JavaScript Map instead of java.util.Map
def select(sql: String): Array[java.util.Map[String, String]] = {
defining(PluginConnectionHolder.threadLocal.get){ conn =>
using(conn.prepareStatement(sql)){ stmt =>
using(stmt.executeQuery()){ rs =>
val list = new java.util.ArrayList[java.util.Map[String, String]]()
while(rs.next){
defining(rs.getMetaData){ meta =>
val map = new java.util.HashMap[String, String]()
Range(1, meta.getColumnCount).map { i =>
val name = meta.getColumnName(i)
map.put(name, rs.getString(name))
}
list.add(map)
}
}
list.toArray(new Array[java.util.Map[String, String]](list.size))
}
}
}
}
}
} }
object JavaScriptPlugin { object JavaScriptPlugin {

View File

@@ -1,6 +1,7 @@
package plugin package plugin
import plugin.PluginSystem.{Action, GlobalMenu, RepositoryMenu} import plugin.PluginSystem._
import java.sql.Connection
trait Plugin { trait Plugin {
val id: String val id: String
@@ -11,6 +12,10 @@ trait Plugin {
def repositoryMenus : List[RepositoryMenu] def repositoryMenus : List[RepositoryMenu]
def globalMenus : List[GlobalMenu] def globalMenus : List[GlobalMenu]
def repositoryActions : List[Action] def repositoryActions : List[RepositoryAction]
def globalActions : List[Action] def globalActions : List[Action]
} }
object PluginConnectionHolder {
val threadLocal = new ThreadLocal[Connection]
}

View File

@@ -9,6 +9,7 @@ import util.ControlUtil._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import util.JGitUtil import util.JGitUtil
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import service.RepositoryService.RepositoryInfo
/** /**
* Provides extension points to plug-ins. * Provides extension points to plug-ins.
@@ -78,7 +79,7 @@ object PluginSystem {
def repositoryMenus : List[RepositoryMenu] = pluginsMap.values.flatMap(_.repositoryMenus).toList def repositoryMenus : List[RepositoryMenu] = pluginsMap.values.flatMap(_.repositoryMenus).toList
def globalMenus : List[GlobalMenu] = pluginsMap.values.flatMap(_.globalMenus).toList def globalMenus : List[GlobalMenu] = pluginsMap.values.flatMap(_.globalMenus).toList
def repositoryActions : List[Action] = pluginsMap.values.flatMap(_.repositoryActions).toList def repositoryActions : List[RepositoryAction] = pluginsMap.values.flatMap(_.repositoryActions).toList
def globalActions : List[Action] = pluginsMap.values.flatMap(_.globalActions).toList def globalActions : List[Action] = pluginsMap.values.flatMap(_.globalActions).toList
// Case classes to hold plug-ins information internally in GitBucket // Case classes to hold plug-ins information internally in GitBucket
@@ -86,6 +87,7 @@ object PluginSystem {
case class GlobalMenu(label: String, url: String, icon: String, condition: Context => Boolean) case class GlobalMenu(label: String, url: String, icon: String, condition: Context => Boolean)
case class RepositoryMenu(label: String, name: String, url: String, icon: String, condition: Context => Boolean) case class RepositoryMenu(label: String, name: String, url: String, icon: String, condition: Context => Boolean)
case class Action(path: String, function: (HttpServletRequest, HttpServletResponse) => Any) case class Action(path: String, function: (HttpServletRequest, HttpServletResponse) => Any)
case class RepositoryAction(path: String, function: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any)
/** /**
* Checks whether the plugin is updatable. * Checks whether the plugin is updatable.

View File

@@ -50,18 +50,17 @@ class PluginUpdateJob extends Job {
object PluginUpdateJob { object PluginUpdateJob {
def schedule(scheduler: Scheduler): Unit = { def schedule(scheduler: Scheduler): Unit = {
// TODO Enable commented code to enable plug-in system val job = newJob(classOf[PluginUpdateJob])
// val job = newJob(classOf[PluginUpdateJob]) .withIdentity("pluginUpdateJob")
// .withIdentity("pluginUpdateJob") .build()
// .build()
// val trigger = newTrigger()
// val trigger = newTrigger() .withIdentity("pluginUpdateTrigger")
// .withIdentity("pluginUpdateTrigger") .startNow()
// .startNow() .withSchedule(simpleSchedule().withIntervalInHours(24).repeatForever())
// .withSchedule(simpleSchedule().withIntervalInHours(24).repeatForever()) .build()
// .build()
// scheduler.scheduleJob(job, trigger)
// scheduler.scheduleJob(job, trigger)
} }
} }

View File

@@ -2,8 +2,9 @@ package plugin
import app.Context import app.Context
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import plugin.PluginSystem.{Action, GlobalMenu, RepositoryMenu} import plugin.PluginSystem._
import javax.servlet.http.{HttpServletResponse, HttpServletRequest} import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import service.RepositoryService.RepositoryInfo
// TODO This is a sample implementation for Scala based plug-ins. // TODO This is a sample implementation for Scala based plug-ins.
class ScalaPlugin(val id: String, val version: String, class ScalaPlugin(val id: String, val version: String,
@@ -11,12 +12,12 @@ class ScalaPlugin(val id: String, val version: String,
private val repositoryMenuList = ListBuffer[RepositoryMenu]() private val repositoryMenuList = ListBuffer[RepositoryMenu]()
private val globalMenuList = ListBuffer[GlobalMenu]() private val globalMenuList = ListBuffer[GlobalMenu]()
private val repositoryActionList = ListBuffer[Action]() private val repositoryActionList = ListBuffer[RepositoryAction]()
private val globalActionList = ListBuffer[Action]() private val globalActionList = ListBuffer[Action]()
def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList
def globalMenus : List[GlobalMenu] = globalMenuList.toList def globalMenus : List[GlobalMenu] = globalMenuList.toList
def repositoryActions : List[Action] = repositoryActionList.toList def repositoryActions : List[RepositoryAction] = repositoryActionList.toList
def globalActions : List[Action] = globalActionList.toList def globalActions : List[Action] = globalActionList.toList
def addRepositoryMenu(label: String, name: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = { def addRepositoryMenu(label: String, name: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = {
@@ -31,8 +32,8 @@ class ScalaPlugin(val id: String, val version: String,
globalActionList += Action(path, function) globalActionList += Action(path, function)
} }
def addRepositoryAction(path: String)(function: (HttpServletRequest, HttpServletResponse) => Any): Unit = { def addRepositoryAction(path: String)(function: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any): Unit = {
repositoryActionList += Action(path, function) repositoryActionList += RepositoryAction(path, function)
} }
} }

View File

@@ -1,9 +1,10 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.{Account, GroupMember}
// TODO [Slick 2.0]NOT import directly? // TODO [Slick 2.0]NOT import directly?
import model.dateColumnType import model.Profile.dateColumnType
import service.SystemSettingsService.SystemSettings import service.SystemSettingsService.SystemSettings
import util.StringUtil._ import util.StringUtil._
import util.LDAPUtil import util.LDAPUtil
@@ -39,7 +40,11 @@ trait AccountService {
// Create or update account by LDAP information // Create or update account by LDAP information
getAccountByUserName(ldapUserInfo.userName, true) match { getAccountByUserName(ldapUserInfo.userName, true) match {
case Some(x) if(!x.isRemoved) => { case Some(x) if(!x.isRemoved) => {
if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) {
updateAccount(x.copy(fullName = ldapUserInfo.fullName))
} else {
updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
}
getAccountByUserName(ldapUserInfo.userName) getAccountByUserName(ldapUserInfo.userName)
} }
case Some(x) if(x.isRemoved) => { case Some(x) if(x.isRemoved) => {
@@ -70,16 +75,16 @@ trait AccountService {
} }
def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
Accounts filter(t => (t.userName is userName.bind) && (t.removed is false.bind, !includeRemoved)) firstOption Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
Accounts filter(t => (t.mailAddress.toLowerCase is mailAddress.toLowerCase.bind) && (t.removed is false.bind, !includeRemoved)) firstOption Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] = def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] =
if(includeRemoved){ if(includeRemoved){
Accounts sortBy(_.userName) list Accounts sortBy(_.userName) list
} else { } else {
Accounts filter (_.removed is false.bind) sortBy(_.userName) list Accounts filter (_.removed === false.bind) sortBy(_.userName) list
} }
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String]) def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
@@ -100,7 +105,7 @@ trait AccountService {
def updateAccount(account: Account)(implicit s: Session): Unit = def updateAccount(account: Account)(implicit s: Session): Unit =
Accounts Accounts
.filter { a => a.userName is account.userName.bind } .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) } .map { a => (a.password, a.fullName, a.mailAddress, a.isAdmin, a.url.?, a.registeredDate, a.updatedDate, a.lastLoginDate.?, a.removed) }
.update ( .update (
account.password, account.password,
@@ -114,10 +119,10 @@ trait AccountService {
account.isRemoved) account.isRemoved)
def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit = def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit =
Accounts.filter(_.userName is userName.bind).map(_.image.?).update(image) Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image)
def updateLastLoginDate(userName: String)(implicit s: Session): Unit = def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
Accounts.filter(_.userName is userName.bind).map(_.lastLoginDate).update(currentDate) Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate)
def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit = def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit =
Accounts insert Account( Accounts insert Account(
@@ -135,10 +140,10 @@ trait AccountService {
isRemoved = false) isRemoved = false)
def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit = def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit =
Accounts.filter(_.userName is groupName.bind).map(t => t.url.? -> t.removed).update(url, removed) 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 = { def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
GroupMembers.filter(_.groupName is groupName.bind).delete GroupMembers.filter(_.groupName === groupName.bind).delete
members.foreach { case (userName, isManager) => members.foreach { case (userName, isManager) =>
GroupMembers insert GroupMember (groupName, userName, isManager) GroupMembers insert GroupMember (groupName, userName, isManager)
} }
@@ -146,21 +151,21 @@ trait AccountService {
def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] = def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] =
GroupMembers GroupMembers
.filter(_.groupName is groupName.bind) .filter(_.groupName === groupName.bind)
.sortBy(_.userName) .sortBy(_.userName)
.list .list
def getGroupsByUserName(userName: String)(implicit s: Session): List[String] = def getGroupsByUserName(userName: String)(implicit s: Session): List[String] =
GroupMembers GroupMembers
.filter(_.userName is userName.bind) .filter(_.userName === userName.bind)
.sortBy(_.groupName) .sortBy(_.groupName)
.map(_.groupName) .map(_.groupName)
.list .list
def removeUserRelatedData(userName: String)(implicit s: Session): Unit = { def removeUserRelatedData(userName: String)(implicit s: Session): Unit = {
GroupMembers.filter(_.userName is userName.bind).delete GroupMembers.filter(_.userName === userName.bind).delete
Collaborators.filter(_.collaboratorName is userName.bind).delete Collaborators.filter(_.collaboratorName === userName.bind).delete
Repositories.filter(_.userName is userName.bind).delete Repositories.filter(_.userName === userName.bind).delete
} }
} }

View File

@@ -1,7 +1,8 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.Activity
trait ActivityService { trait ActivityService {
@@ -10,9 +11,9 @@ trait ActivityService {
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter { case (t1, t2) => .filter { case (t1, t2) =>
if(isPublic){ if(isPublic){
(t1.activityUserName is activityUserName.bind) && (t2.isPrivate is false.bind) (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind)
} else { } else {
(t1.activityUserName is activityUserName.bind) (t1.activityUserName === activityUserName.bind)
} }
} }
.sortBy { case (t1, t2) => t1.activityId desc } .sortBy { case (t1, t2) => t1.activityId desc }
@@ -23,7 +24,16 @@ trait ActivityService {
def getRecentActivities()(implicit s: Session): List[Activity] = def getRecentActivities()(implicit s: Session): List[Activity] =
Activities Activities
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter { case (t1, t2) => t2.isPrivate is false.bind } .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])(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 } .sortBy { case (t1, t2) => t1.activityId desc }
.map { case (t1, t2) => t1 } .map { case (t1, t2) => t1 }
.take(30) .take(30)

View File

@@ -3,8 +3,9 @@ package service
import scala.slick.jdbc.{StaticQuery => Q} import scala.slick.jdbc.{StaticQuery => Q}
import Q.interpolation import Q.interpolation
import model._ import model.Profile._
import simple._ import profile.simple._
import model.{Issue, IssueComment, IssueLabel, Label}
import util.Implicits._ import util.Implicits._
import util.StringUtil._ import util.StringUtil._
@@ -49,7 +50,6 @@ trait IssuesService {
*/ */
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
repos: (String, String)*)(implicit s: Session): Int = repos: (String, String)*)(implicit s: Session): Int =
// TODO check SQL
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
/** /**
@@ -166,13 +166,13 @@ trait IssuesService {
.getOrElse (repos) .getOrElse (repos)
.map { case (owner, repository) => t1.byRepository(owner, repository) } .map { case (owner, repository) => t1.byRepository(owner, repository) }
.foldLeft[Column[Boolean]](false) ( _ || _ ) && .foldLeft[Column[Boolean]](false) ( _ || _ ) &&
(t1.closed is (condition.state == "closed").bind) && (t1.closed === (condition.state == "closed").bind) &&
(t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) && (t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
(t1.milestoneId isNull, condition.milestoneId == Some(None)) && (t1.milestoneId.? isEmpty, condition.milestoneId == Some(None)) &&
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) && (t1.assignedUserName === filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) && (t1.openedUserName === filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
(t1.openedUserName isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) && (t1.openedUserName =!= filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
(t1.pullRequest is true.bind, onlyPullRequest) && (t1.pullRequest === true.bind, onlyPullRequest) &&
(IssueLabels filter { t2 => (IssueLabels filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
(t2.labelId in (t2.labelId in

View File

@@ -1,7 +1,8 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.Label
trait LabelsService { trait LabelsService {

View File

@@ -1,9 +1,10 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.Milestone
// TODO [Slick 2.0]NOT import directly? // TODO [Slick 2.0]NOT import directly?
import model.dateColumnType import model.Profile.dateColumnType
trait MilestonesService { trait MilestonesService {
@@ -40,7 +41,7 @@ trait MilestonesService {
def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = { def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = {
val counts = Issues val counts = Issues
.filter { t => (t.byRepository(owner, repository)) && (t.milestoneId isNotNull) } .filter { t => (t.byRepository(owner, repository)) && (t.milestoneId.? isDefined) }
.groupBy { t => t.milestoneId -> t.closed } .groupBy { t => t.milestoneId -> t.closed }
.map { case (t1, t2) => t1._1 -> t1._2 -> t2.length } .map { case (t1, t2) => t1._1 -> t1._2 -> t2.length }
.toMap .toMap

View File

@@ -1,7 +1,8 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.{PullRequest, Issue}
trait PullRequestService { self: IssuesService => trait PullRequestService { self: IssuesService =>
import PullRequestService._ import PullRequestService._
@@ -25,9 +26,9 @@ trait PullRequestService { self: IssuesService =>
PullRequests PullRequests
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
.filter { case (t1, t2) => .filter { case (t1, t2) =>
(t2.closed is closed.bind) && (t2.closed === closed.bind) &&
(t1.userName is owner.get.bind, owner.isDefined) && (t1.userName === owner.get.bind, owner.isDefined) &&
(t1.repositoryName is repository.get.bind, repository.isDefined) (t1.repositoryName === repository.get.bind, repository.isDefined)
} }
.groupBy { case (t1, t2) => t2.openedUserName } .groupBy { case (t1, t2) => t2.openedUserName }
.map { case (userName, t) => userName -> t.length } .map { case (userName, t) => userName -> t.length }
@@ -54,10 +55,10 @@ trait PullRequestService { self: IssuesService =>
PullRequests PullRequests
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
.filter { case (t1, t2) => .filter { case (t1, t2) =>
(t1.requestUserName is userName.bind) && (t1.requestUserName === userName.bind) &&
(t1.requestRepositoryName is repositoryName.bind) && (t1.requestRepositoryName === repositoryName.bind) &&
(t1.requestBranch is branch.bind) && (t1.requestBranch === branch.bind) &&
(t2.closed is closed.bind) (t2.closed === closed.bind)
} }
.map { case (t1, t2) => t1 } .map { case (t1, t2) => t1 }
.list .list

View File

@@ -7,8 +7,8 @@ import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.treewalk.TreeWalk import org.eclipse.jgit.treewalk.TreeWalk
import org.eclipse.jgit.lib.FileMode import org.eclipse.jgit.lib.FileMode
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import model._ import model.Profile._
import simple._ import profile.simple._
trait RepositorySearchService { self: IssuesService => trait RepositorySearchService { self: IssuesService =>
import RepositorySearchService._ import RepositorySearchService._
@@ -107,7 +107,7 @@ object RepositorySearchService {
case class SearchResult( case class SearchResult(
files : List[(String, String)], files : List[(String, String)],
issues: List[(Issue, Int, String)]) issues: List[(model.Issue, Int, String)])
case class IssueSearchResult( case class IssueSearchResult(
issueId: Int, issueId: Int,

View File

@@ -1,7 +1,8 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.{Repository, Account, Collaborator}
import util.JGitUtil import util.JGitUtil
trait RepositoryService { self: AccountService => trait RepositoryService { self: AccountService =>
@@ -57,15 +58,15 @@ trait RepositoryService { self: AccountService =>
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
Repositories.filter { t => Repositories.filter { t =>
(t.originUserName is oldUserName.bind) && (t.originRepositoryName is oldRepositoryName.bind) (t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
}.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName) }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
Repositories.filter { t => Repositories.filter { t =>
(t.parentUserName is oldUserName.bind) && (t.parentRepositoryName is oldRepositoryName.bind) (t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind)
}.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName) }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
PullRequests.filter { t => PullRequests.filter { t =>
t.requestRepositoryName is oldRepositoryName.bind t.requestRepositoryName === oldRepositoryName.bind
}.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName) }.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
deleteRepository(oldUserName, oldRepositoryName) deleteRepository(oldUserName, oldRepositoryName)
@@ -73,7 +74,16 @@ trait RepositoryService { self: AccountService =>
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*) IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
Issues .insertAll(issues .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
Issues.insertAll(issues.map { x => x.copy(
userName = newUserName,
repositoryName = newRepositoryName,
milestoneId = x.milestoneId.map { id =>
newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId
}
)} :_*)
PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
@@ -92,7 +102,7 @@ trait RepositoryService { self: AccountService =>
}.map { t => t.activityId -> t.message }.list }.map { t => t.activityId -> t.message }.list
updateActivities.foreach { case (activityId, message) => updateActivities.foreach { case (activityId, message) =>
Activities.filter(_.activityId is activityId.bind).map(_.message).update( Activities.filter(_.activityId === activityId.bind).map(_.message).update(
message message
.replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]") .replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]")
.replace(s"[branch:${oldUserName}/${oldRepositoryName}#" ,s"[branch:${newUserName}/${newRepositoryName}#") .replace(s"[branch:${oldUserName}/${oldRepositoryName}#" ,s"[branch:${newUserName}/${newRepositoryName}#")
@@ -126,7 +136,7 @@ trait RepositoryService { self: AccountService =>
* @return the list of repository names * @return the list of repository names
*/ */
def getRepositoryNamesOfUser(userName: String)(implicit s: Session): List[String] = def getRepositoryNamesOfUser(userName: String)(implicit s: Session): List[String] =
Repositories filter(_.userName is userName.bind) map (_.repositoryName) list Repositories filter(_.userName === userName.bind) map (_.repositoryName) list
/** /**
* Returns the specified repository information. * Returns the specified repository information.
@@ -140,7 +150,7 @@ trait RepositoryService { self: AccountService =>
(Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository => (Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
// for getting issue count and pull request count // for getting issue count and pull request count
val issues = Issues.filter { t => val issues = Issues.filter { t =>
t.byRepository(repository.userName, repository.repositoryName) && (t.closed is false.bind) t.byRepository(repository.userName, repository.repositoryName) && (t.closed === false.bind)
}.map(_.pullRequest).list }.map(_.pullRequest).list
new RepositoryInfo( new RepositoryInfo(
@@ -156,11 +166,17 @@ trait RepositoryService { self: AccountService =>
} }
} }
def getAllRepositories()(implicit s: Session): List[(String, String)] = {
Repositories.sortBy(_.lastActivityDate desc).map{ t =>
(t.userName, t.repositoryName)
}.list
}
def getUserRepositories(userName: String, baseUrl: String, withoutPhysicalInfo: Boolean = false) def getUserRepositories(userName: String, baseUrl: String, withoutPhysicalInfo: Boolean = false)
(implicit s: Session): List[RepositoryInfo] = { (implicit s: Session): List[RepositoryInfo] = {
Repositories.filter { t1 => Repositories.filter { t1 =>
(t1.userName is userName.bind) || (t1.userName === userName.bind) ||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName is userName.bind)} exists) (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists)
}.sortBy(_.lastActivityDate desc).list.map{ repository => }.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo( new RepositoryInfo(
if(withoutPhysicalInfo){ if(withoutPhysicalInfo){
@@ -196,13 +212,13 @@ trait RepositoryService { self: AccountService =>
case Some(x) if(x.isAdmin) => Repositories case Some(x) if(x.isAdmin) => Repositories
// for Normal Users // for Normal Users
case Some(x) if(!x.isAdmin) => case Some(x) if(!x.isAdmin) =>
Repositories filter { t => (t.isPrivate is false.bind) || (t.userName is x.userName) || Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) ||
(Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName is x.userName.bind)} exists) (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists)
} }
// for Guests // for Guests
case None => Repositories filter(_.isPrivate is false.bind) case None => Repositories filter(_.isPrivate === false.bind)
}).filter { t => }).filter { t =>
repositoryUserName.map { userName => t.userName is userName.bind } getOrElse LiteralColumn(true) repositoryUserName.map { userName => t.userName === userName.bind } getOrElse LiteralColumn(true)
}.sortBy(_.lastActivityDate desc).list.map{ repository => }.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo( new RepositoryInfo(
if(withoutPhysicalInfo){ if(withoutPhysicalInfo){
@@ -290,15 +306,14 @@ trait RepositoryService { self: AccountService =>
} }
private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int = private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
// TODO check SQL
Query(Repositories.filter { t => Query(Repositories.filter { t =>
(t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind) (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
}.length).first }.length).first
def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[(String, String)] = def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[(String, String)] =
Repositories.filter { t => Repositories.filter { t =>
(t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind) (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
} }
.sortBy(_.userName asc).map(t => t.userName -> t.repositoryName).list .sortBy(_.userName asc).map(t => t.userName -> t.repositoryName).list

View File

@@ -1,7 +1,6 @@
package service package service
import model._ import model.{Account, Issue, Session}
import slick.jdbc.JdbcBackend
import util.Implicits.request2Session import util.Implicits.request2Session
/** /**
@@ -12,7 +11,7 @@ import util.Implicits.request2Session
*/ */
trait RequestCache extends SystemSettingsService with AccountService with IssuesService { trait RequestCache extends SystemSettingsService with AccountService with IssuesService {
private implicit def context2Session(implicit context: app.Context): JdbcBackend#Session = private implicit def context2Session(implicit context: app.Context): Session =
request2Session(context.request) request2Session(context.request)
def getIssue(userName: String, repositoryName: String, issueId: String) def getIssue(userName: String, repositoryName: String, issueId: String)

View File

@@ -1,7 +1,8 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.SshKey
trait SshKeyService { trait SshKeyService {
@@ -9,7 +10,7 @@ trait SshKeyService {
SshKeys insert SshKey(userName = userName, title = title, publicKey = publicKey) SshKeys insert SshKey(userName = userName, title = title, publicKey = publicKey)
def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] = def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
SshKeys.filter(_.userName is userName.bind).sortBy(_.sshKeyId).list SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit = def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete

View File

@@ -37,8 +37,9 @@ trait SystemSettingsService {
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x)) ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
props.setProperty(LdapBaseDN, ldap.baseDN) props.setProperty(LdapBaseDN, ldap.baseDN)
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute) props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x)) ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute) ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x.toString))
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString)) ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x)) ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
} }
@@ -85,8 +86,9 @@ trait SystemSettingsService {
getOptionValue(props, LdapBindPassword, None), getOptionValue(props, LdapBindPassword, None),
getValue(props, LdapBaseDN, ""), getValue(props, LdapBaseDN, ""),
getValue(props, LdapUserNameAttribute, ""), getValue(props, LdapUserNameAttribute, ""),
getOptionValue(props, LdapAdditionalFilterCondition, None),
getOptionValue(props, LdapFullNameAttribute, None), getOptionValue(props, LdapFullNameAttribute, None),
getValue(props, LdapMailAddressAttribute, ""), getOptionValue(props, LdapMailAddressAttribute, None),
getOptionValue[Boolean](props, LdapTls, None), getOptionValue[Boolean](props, LdapTls, None),
getOptionValue(props, LdapKeystore, None))) getOptionValue(props, LdapKeystore, None)))
} else { } else {
@@ -125,8 +127,9 @@ object SystemSettingsService {
bindPassword: Option[String], bindPassword: Option[String],
baseDN: String, baseDN: String,
userNameAttribute: String, userNameAttribute: String,
additionalFilterCondition: Option[String],
fullNameAttribute: Option[String], fullNameAttribute: Option[String],
mailAttribute: String, mailAttribute: Option[String],
tls: Option[Boolean], tls: Option[Boolean],
keystore: Option[String]) keystore: Option[String])
@@ -163,6 +166,7 @@ object SystemSettingsService {
private val LdapBindPassword = "ldap.bind_password" private val LdapBindPassword = "ldap.bind_password"
private val LdapBaseDN = "ldap.baseDN" private val LdapBaseDN = "ldap.baseDN"
private val LdapUserNameAttribute = "ldap.username_attribute" private val LdapUserNameAttribute = "ldap.username_attribute"
private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition"
private val LdapFullNameAttribute = "ldap.fullname_attribute" private val LdapFullNameAttribute = "ldap.fullname_attribute"
private val LdapMailAddressAttribute = "ldap.mail_attribute" private val LdapMailAddressAttribute = "ldap.mail_attribute"
private val LdapTls = "ldap.tls" private val LdapTls = "ldap.tls"

View File

@@ -1,7 +1,8 @@
package service package service
import model._ import model.Profile._
import simple._ import profile.simple._
import model.{WebHook, Account}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import service.RepositoryService.RepositoryInfo import service.RepositoryService.RepositoryInfo
import util.JGitUtil import util.JGitUtil
@@ -43,7 +44,7 @@ trait WebHookService {
val httpClient = HttpClientBuilder.create.build val httpClient = HttpClientBuilder.create.build
webHookURLs.foreach { webHookUrl => webHookURLs.foreach { webHookUrl =>
val f = future { val f = Future {
logger.debug(s"start web hook invocation for ${webHookUrl}") logger.debug(s"start web hook invocation for ${webHookUrl}")
val httpPost = new HttpPost(webHookUrl.url) val httpPost = new HttpPost(webHookUrl.url)
@@ -89,15 +90,15 @@ object WebHookService {
WebHookCommit( WebHookCommit(
id = commit.id, id = commit.id,
message = commit.fullMessage, message = commit.fullMessage,
timestamp = commit.time.toString, timestamp = commit.commitTime.toString,
url = commitUrl, url = commitUrl,
added = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD) => x.newPath }, added = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD) => x.newPath },
removed = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.DELETE) => x.oldPath }, removed = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.DELETE) => x.oldPath },
modified = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD && modified = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD &&
x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath }, x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath },
author = WebHookUser( author = WebHookUser(
name = commit.committer, name = commit.committerName,
email = commit.mailAddress email = commit.committerEmailAddress
) )
) )
}, },

View File

@@ -64,7 +64,7 @@ trait WikiService {
if(!JGitUtil.isEmpty(git)){ if(!JGitUtil.isEmpty(git)){
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file => JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes), WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
file.committer, file.time, file.commitId) file.author, file.time, file.commitId)
} }
} else None } else None
} }

View File

@@ -52,6 +52,7 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current BitBucket version. * The history of versions. A head of this sequence is the current BitBucket version.
*/ */
val versions = Seq( val versions = Seq(
new Version(2, 2),
new Version(2, 1), new Version(2, 1),
new Version(2, 0){ new Version(2, 0){
override def update(conn: Connection): Unit = { override def update(conn: Connection): Unit = {

View File

@@ -5,7 +5,6 @@ import javax.servlet.http._
import service.{SystemSettingsService, AccountService, RepositoryService} import service.{SystemSettingsService, AccountService, RepositoryService}
import model._ import model._
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import slick.jdbc.JdbcBackend
import util.Implicits._ import util.Implicits._
import util.ControlUtil._ import util.ControlUtil._
import util.Keys import util.Keys
@@ -67,7 +66,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
} }
private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo) private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo)
(implicit session: JdbcBackend#Session): Option[Account] = (implicit session: Session): Option[Account] =
authenticate(loadSystemSettings(), username, password) match { authenticate(loadSystemSettings(), username, password) match {
case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x
case _ => None case _ => None

View File

@@ -17,7 +17,7 @@ import WebHookService._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import util.JGitUtil.CommitInfo import util.JGitUtil.CommitInfo
import service.IssuesService.IssueSearchCondition import service.IssuesService.IssueSearchCondition
import slick.jdbc.JdbcBackend import model.Session
/** /**
* Provides Git repository via HTTP. * Provides Git repository via HTTP.
@@ -95,7 +95,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: JdbcBackend#Session) class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session)
extends PostReceiveHook with PreReceiveHook extends PostReceiveHook with PreReceiveHook
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService { with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService {
@@ -205,7 +205,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
private def createIssueComment(commit: CommitInfo) = { private def createIssueComment(commit: CommitInfo) = {
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId => StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){ if(getIssue(owner, repository, issueId).isDefined){
getAccountByMailAddress(commit.mailAddress).foreach { account => getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit") createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
} }
} }

View File

@@ -5,8 +5,11 @@ import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import twirl.api.Html import twirl.api.Html
import service.{AccountService, RepositoryService, SystemSettingsService} import service.{AccountService, RepositoryService, SystemSettingsService}
import model.Account import model.{Account, Session}
import util.{JGitUtil, Keys} import util.{JGitUtil, Keys}
import plugin.PluginConnectionHolder
import service.RepositoryService.RepositoryInfo
import service.SystemSettingsService.SystemSettings
class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService { class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService {
@@ -30,18 +33,14 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse): Boolean = { private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse): Boolean = {
plugin.PluginSystem.globalActions.find(_.path == path).map { action => plugin.PluginSystem.globalActions.find(_.path == path).map { action =>
val result = action.function(request, response) val result = action.function(request, response)
val systemSettings = loadSystemSettings()
result match { result match {
case x: String => { case x: String => renderGlobalHtml(request, response, systemSettings, x)
response.setContentType("text/html; charset=UTF-8") case x: org.mozilla.javascript.NativeObject => {
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] x.get("format") match {
implicit val context = app.Context(loadSystemSettings(), Option(loginAccount), request) case "html" => renderGlobalHtml(request, response, systemSettings, x.get("body").toString)
val html = _root_.html.main("GitBucket", None)(Html(x)) case "json" => renderJson(request, response, x.get("body").toString)
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
} }
case x => {
// TODO returns as JSON?
response.setContentType("application/json; charset=UTF-8")
} }
} }
true true
@@ -49,27 +48,28 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
} }
private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse) private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
(implicit session: model.simple.Session): Boolean = { (implicit session: Session): Boolean = {
val elements = path.split("/") val elements = path.split("/")
if(elements.length > 3){ if(elements.length > 3){
val owner = elements(1) val owner = elements(1)
val name = elements(2) val name = elements(2)
val remain = elements.drop(3).mkString("/", "/", "") val remain = elements.drop(3).mkString("/", "/", "")
getRepository(owner, name, "").flatMap { repository => // TODO fill baseUrl val systemSettings = loadSystemSettings()
getRepository(owner, name, systemSettings.baseUrl(request)).flatMap { repository =>
plugin.PluginSystem.repositoryActions.find(_.path == remain).map { action => plugin.PluginSystem.repositoryActions.find(_.path == remain).map { action =>
val result = action.function(request, response) val result = try {
result match { PluginConnectionHolder.threadLocal.set(session.conn)
case x: String => { action.function(request, response, repository)
response.setContentType("text/html; charset=UTF-8") } finally {
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] PluginConnectionHolder.threadLocal.remove()
implicit val context = app.Context(loadSystemSettings(), Option(loginAccount), request) }
val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(x))) // TODO specify active side menu result match {
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream) case x: String => renderRepositoryHtml(request, response, systemSettings, repository, x)
case x: org.mozilla.javascript.NativeObject => {
x.get("format") match {
case "html" => renderRepositoryHtml(request, response, systemSettings, repository, x.get("body").toString)
case "json" => renderJson(request, response, x.get("body").toString)
} }
case x => {
// TODO returns as JSON?
response.setContentType("application/json; charset=UTF-8")
} }
} }
true true
@@ -78,4 +78,27 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
} else false } else false
} }
private def renderGlobalHtml(request: HttpServletRequest, response: HttpServletResponse,
systemSettings: SystemSettings, body: String): Unit = {
response.setContentType("text/html; charset=UTF-8")
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
val html = _root_.html.main("GitBucket", None)(Html(body))
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
}
private def renderRepositoryHtml(request: HttpServletRequest, response: HttpServletResponse,
systemSettings: SystemSettings, repository: RepositoryInfo, body: String): Unit = {
response.setContentType("text/html; charset=UTF-8")
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(body))) // TODO specify active side menu
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
}
private def renderJson(request: HttpServletRequest, response: HttpServletResponse, body: String): Unit = {
response.setContentType("application/json; charset=UTF-8")
IOUtils.write(body.getBytes("UTF-8"), response.getOutputStream)
}
} }

View File

@@ -12,7 +12,7 @@ import servlet.{Database, CommitLogHook}
import service.{AccountService, RepositoryService, SystemSettingsService} import service.{AccountService, RepositoryService, SystemSettingsService}
import org.eclipse.jgit.errors.RepositoryNotFoundException import org.eclipse.jgit.errors.RepositoryNotFoundException
import javax.servlet.ServletContext import javax.servlet.ServletContext
import model.profile.simple.Session import model.Session
object GitCommand { object GitCommand {
val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
@@ -31,7 +31,7 @@ abstract class GitCommand(val context: ServletContext, val owner: String, val re
private def newTask(user: String): Runnable = new Runnable { private def newTask(user: String): Runnable = new Runnable {
override def run(): Unit = { override def run(): Unit = {
Database(context) withTransaction { implicit session => Database(context) withSession { implicit session =>
try { try {
runTask(user) runTask(user)
callback.onExit(0) callback.onExit(0)

View File

@@ -10,7 +10,7 @@ import javax.servlet.ServletContext
class PublicKeyAuthenticator(context: ServletContext) extends PublickeyAuthenticator with SshKeyService { class PublicKeyAuthenticator(context: ServletContext) extends PublickeyAuthenticator with SshKeyService {
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = { override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
Database(context) withTransaction { implicit session => Database(context) withSession { implicit session =>
getPublicKeys(username).exists { sshKey => getPublicKeys(username).exists { sshKey =>
SshUtil.str2PublicKey(sshKey.publicKey) match { SshUtil.str2PublicKey(sshKey.publicKey) match {
case Some(publicKey) => key.equals(publicKey) case Some(publicKey) => key.equals(publicKey)

View File

@@ -47,38 +47,45 @@ object JGitUtil {
* @param id the object id * @param id the object id
* @param isDirectory whether is it directory * @param isDirectory whether is it directory
* @param name the file (or directory) name * @param name the file (or directory) name
* @param time the last modified time
* @param message the last commit message * @param message the last commit message
* @param commitId the last commit id * @param commitId the last commit id
* @param committer the last committer name * @param time the last modified time
* @param author the last committer name
* @param mailAddress the committer's mail address * @param mailAddress the committer's mail address
* @param linkUrl the url of submodule * @param linkUrl the url of submodule
*/ */
case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, time: Date, message: String, commitId: String, case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, message: String, commitId: String,
committer: String, mailAddress: String, linkUrl: Option[String]) time: Date, author: String, mailAddress: String, linkUrl: Option[String])
/** /**
* The commit data. * The commit data.
* *
* @param id the commit id * @param id the commit id
* @param time the commit time
* @param committer the committer name
* @param mailAddress the mail address of the committer
* @param shortMessage the short message * @param shortMessage the short message
* @param fullMessage the full message * @param fullMessage the full message
* @param parents the list of parent commit id * @param parents the list of parent commit id
* @param authorTime the author time
* @param authorName the author name
* @param authorEmailAddress the mail address of the author
* @param commitTime the commit time
* @param committerName the committer name
* @param committerEmailAddress the mail address of the committer
*/ */
case class CommitInfo(id: String, time: Date, committer: String, mailAddress: String, case class CommitInfo(id: String, shortMessage: String, fullMessage: String, parents: List[String],
shortMessage: String, fullMessage: String, parents: List[String]){ authorTime: Date, authorName: String, authorEmailAddress: String,
commitTime: Date, committerName: String, committerEmailAddress: String){
def this(rev: org.eclipse.jgit.revwalk.RevCommit) = this( def this(rev: org.eclipse.jgit.revwalk.RevCommit) = this(
rev.getName, rev.getName,
rev.getCommitterIdent.getWhen,
rev.getCommitterIdent.getName,
rev.getCommitterIdent.getEmailAddress,
rev.getShortMessage, rev.getShortMessage,
rev.getFullMessage, rev.getFullMessage,
rev.getParents().map(_.name).toList) rev.getParents().map(_.name).toList,
rev.getAuthorIdent.getWhen,
rev.getAuthorIdent.getName,
rev.getAuthorIdent.getEmailAddress,
rev.getCommitterIdent.getWhen,
rev.getCommitterIdent.getName,
rev.getCommitterIdent.getEmailAddress)
val summary = getSummaryMessage(fullMessage, shortMessage) val summary = getSummaryMessage(fullMessage, shortMessage)
@@ -87,6 +94,8 @@ object JGitUtil {
Some(fullMessage.trim.substring(i).trim) Some(fullMessage.trim.substring(i).trim)
} else None } else None
} }
def isDifferentFromAuthor: Boolean = authorName != committerName || authorEmailAddress != committerEmailAddress
} }
case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String]) case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String])
@@ -98,7 +107,12 @@ object JGitUtil {
* @param content the string content * @param content the string content
* @param charset the character encoding * @param charset the character encoding
*/ */
case class ContentInfo(viewType: String, content: Option[String], charset: Option[String]) case class ContentInfo(viewType: String, content: Option[String], charset: Option[String]){
/**
* the line separator of this content ("LF" or "CRLF")
*/
val lineSeparator: String = if(content.exists(_.indexOf("\r\n") >= 0)) "CRLF" else "LF"
}
/** /**
* The tag data. * The tag data.
@@ -226,11 +240,11 @@ object JGitUtil {
objectId, objectId,
fileMode == FileMode.TREE || fileMode == FileMode.GITLINK, fileMode == FileMode.TREE || fileMode == FileMode.GITLINK,
name, name,
commit.getCommitterIdent.getWhen,
getSummaryMessage(commit.getFullMessage, commit.getShortMessage), getSummaryMessage(commit.getFullMessage, commit.getShortMessage),
commit.getName, commit.getName,
commit.getCommitterIdent.getName, commit.getAuthorIdent.getWhen,
commit.getCommitterIdent.getEmailAddress, commit.getAuthorIdent.getName,
commit.getAuthorIdent.getEmailAddress,
linkUrl) linkUrl)
} }
}.sortWith { (file1, file2) => }.sortWith { (file1, file2) =>

View File

@@ -7,6 +7,7 @@ import java.security.Security
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import service.SystemSettingsService.Ldap import service.SystemSettingsService.Ldap
import scala.annotation.tailrec import scala.annotation.tailrec
import model.Account
/** /**
* Utility for LDAP authentication. * Utility for LDAP authentication.
@@ -16,6 +17,26 @@ object LDAPUtil {
private val LDAP_VERSION: Int = LDAPConnection.LDAP_V3 private val LDAP_VERSION: Int = LDAPConnection.LDAP_V3
private val logger = LoggerFactory.getLogger(getClass().getName()) private val logger = LoggerFactory.getLogger(getClass().getName())
private val LDAP_DUMMY_MAL = "@ldap-devnull"
/**
* Returns true if mail address ends with "@ldap-devnull"
*/
def isDummyMailAddress(account: Account): Boolean = {
account.mailAddress.endsWith(LDAP_DUMMY_MAL)
}
/**
* Creates dummy address (userName@ldap-devnull) for LDAP login.
*
* If mail address is not managed in LDAP server, GitBucket stores this dummy address in first LDAP login.
* GitBucket does not send any mails to this dummy address. And these users must input their mail address
* at the first step after LDAP authentication.
*/
def createDummyMailAddress(userName: String): String = {
userName + LDAP_DUMMY_MAL
}
/** /**
* Try authentication by LDAP using given configuration. * Try authentication by LDAP using given configuration.
* Returns Right(LDAPUserInfo) if authentication is successful, otherwise Left(errorMessage). * Returns Right(LDAPUserInfo) if authentication is successful, otherwise Left(errorMessage).
@@ -30,7 +51,7 @@ object LDAPUtil {
keystore = ldapSettings.keystore.getOrElse(""), keystore = ldapSettings.keystore.getOrElse(""),
error = "System LDAP authentication failed." error = "System LDAP authentication failed."
){ conn => ){ conn =>
findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute) match { findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute, ldapSettings.additionalFilterCondition) match {
case Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password) case Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password)
case None => Left("User does not exist.") case None => Left("User does not exist.")
} }
@@ -47,7 +68,15 @@ object LDAPUtil {
keystore = ldapSettings.keystore.getOrElse(""), keystore = ldapSettings.keystore.getOrElse(""),
error = "User LDAP Authentication Failed." error = "User LDAP Authentication Failed."
){ conn => ){ conn =>
findMailAddress(conn, userDN, ldapSettings.userNameAttribute, userName, ldapSettings.mailAttribute) match { if(ldapSettings.mailAttribute.getOrElse("").isEmpty) {
Right(LDAPUserInfo(
userName = userName,
fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
findFullName(conn, userDN, ldapSettings.userNameAttribute, userName, fullNameAttribute)
}.getOrElse(userName),
mailAddress = createDummyMailAddress(userName)))
} else {
findMailAddress(conn, userDN, ldapSettings.userNameAttribute, userName, ldapSettings.mailAttribute.get) match {
case Some(mailAddress) => Right(LDAPUserInfo( case Some(mailAddress) => Right(LDAPUserInfo(
userName = getUserNameFromMailAddress(userName), userName = getUserNameFromMailAddress(userName),
fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute => fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
@@ -58,6 +87,7 @@ object LDAPUtil {
} }
} }
} }
}
private def getUserNameFromMailAddress(userName: String): String = { private def getUserNameFromMailAddress(userName: String): String = {
(userName.indexOf('@') match { (userName.indexOf('@') match {
@@ -112,7 +142,7 @@ object LDAPUtil {
/** /**
* Search a specified user and returns userDN if exists. * Search a specified user and returns userDN if exists.
*/ */
private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String): Option[String] = { private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String, additionalFilterCondition: Option[String]): Option[String] = {
@tailrec @tailrec
def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = { def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = {
if(results.hasMore){ if(results.hasMore){
@@ -125,7 +155,13 @@ object LDAPUtil {
entries.flatten entries.flatten
} }
} }
getEntries(conn.search(baseDN, LDAPConnection.SCOPE_SUB, userNameAttribute + "=" + userName, null, false)).collectFirst {
val filterCond = additionalFilterCondition.getOrElse("") match {
case "" => userNameAttribute + "=" + userName
case x => "(&(" + x + ")(" + userNameAttribute + "=" + userName + "))"
}
getEntries(conn.search(baseDN, LDAPConnection.SCOPE_SUB, filterCond, null, false)).collectFirst {
case x => x.getDN case x => x.getDN
} }
} }

View File

@@ -6,11 +6,11 @@ import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import app.Context import app.Context
import model.Session
import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService} import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
import servlet.Database import servlet.Database
import SystemSettingsService.Smtp import SystemSettingsService.Smtp
import _root_.util.ControlUtil.defining import _root_.util.ControlUtil.defining
import model.profile.simple.Session
trait Notifier extends RepositoryService with AccountService with IssuesService { trait Notifier extends RepositoryService with AccountService with IssuesService {
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
@@ -28,7 +28,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
) )
.distinct .distinct
.withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded .withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
.foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) ) .foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) filterNot (LDAPUtil.isDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) )
} }
@@ -69,8 +69,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
(msg: String => String)(implicit context: Context) = { (msg: String => String)(implicit context: Context) = {
val database = Database(context.request.getServletContext) val database = Database(context.request.getServletContext)
val f = future { val f = Future {
// TODO Can we use the Database Session in other than Transaction Filter?
database withSession { implicit session => database withSession { implicit session =>
getIssue(r.owner, r.name, issueId.toString) foreach { issue => getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
defining( defining(

View File

@@ -46,6 +46,22 @@ object StringUtil {
} }
} }
/**
* Converts line separator in the given content.
*
* @param content the content
* @param lineSeparator "LF" or "CRLF"
* @return the converted content
*/
def convertLineSeparator(content: String, lineSeparator: String): String = {
val lf = content.replace("\r\n", "\n").replace("\r", "\n")
if(lineSeparator == "CRLF"){
lf.replace("\n", "\r\n")
} else {
lf
}
}
/** /**
* Extract issue id like ```#issueId``` from the given message. * Extract issue id like ```#issueId``` from the given message.
* *

View File

@@ -74,7 +74,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
* This method looks up Gravatar if avatar icon has not been configured in user settings. * This method looks up Gravatar if avatar icon has not been configured in user settings.
*/ */
def avatar(commit: util.JGitUtil.CommitInfo, size: Int)(implicit context: app.Context): Html = def avatar(commit: util.JGitUtil.CommitInfo, size: Int)(implicit context: app.Context): Html =
getAvatarImageHtml(commit.committer, size, commit.mailAddress) getAvatarImageHtml(commit.authorName, size, commit.authorEmailAddress)
/** /**
* Converts commit id, issue id and username to the link. * Converts commit id, issue id and username to the link.

View File

@@ -1,6 +1,7 @@
@(account: model.Account, info: Option[Any])(implicit context: app.Context) @(account: model.Account, info: Option[Any])(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import util.LDAPUtil
@html.main("Edit your profile"){ @html.main("Edit your profile"){
<div class="container"> <div class="container">
<div class="row-fluid"> <div class="row-fluid">
@@ -9,6 +10,7 @@
</div> </div>
<div class="span9"> <div class="span9">
@helper.html.information(info) @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"> <form action="@url(account.userName)/_edit" method="POST" validate="true">
<div class="box"> <div class="box">
<div class="box-header">Profile</div> <div class="box-header">Profile</div>
@@ -31,7 +33,7 @@
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="mailAddress" class="strong">Mail Address:</label> <label for="mailAddress" class="strong">Mail Address:</label>
<input type="text" name="mailAddress" id="mailAddress" value="@account.mailAddress"/> <input type="text" name="mailAddress" id="mailAddress" value="@if(!LDAPUtil.isDummyMailAddress(account)){@account.mailAddress}"/>
<span id="error-mailAddress" class="error"></span> <span id="error-mailAddress" class="error"></span>
</fieldset> </fieldset>
<fieldset> <fieldset>
@@ -52,7 +54,7 @@
<a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a> <a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
</div> </div>
<input type="submit" class="btn btn-success" value="Save"/> <input type="submit" class="btn btn-success" value="Save"/>
<a href="@url(account.userName)" class="btn">Cancel</a> @if(!LDAPUtil.isDummyMailAddress(account)){<a href="@url(account.userName)" class="btn">Cancel</a>}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -60,7 +60,7 @@ $(function(){
}); });
$('#addMember').click(function(){ $('#addMember').click(function(){
$('#error-memberName').text(''); $('#error-members').text('');
var userName = $('#memberName').val(); var userName = $('#memberName').val();
// check empty // check empty
@@ -73,18 +73,18 @@ $(function(){
return $(this).data('name') == userName; return $(this).data('name') == userName;
}).length > 0; }).length > 0;
if(exists){ if(exists){
$('#error-memberName').text('User has been already added.'); $('#error-members').text('User has been already added.');
return false; return false;
} }
// check existence // check existence
$.post('@path/admin/users/_usercheck', { $.post('@path/_user/existence', {
'userName': userName 'userName': userName
}, function(data, status){ }, function(data, status){
if(data == 'true'){ if(data == 'true'){
addMemberHTML(userName, false); addMemberHTML(userName, false);
} else { } else {
$('#error-memberName').text('User does not exist.'); $('#error-members').text('User does not exist.');
} }
}); });
}); });

View File

@@ -8,7 +8,7 @@
<div class="row-fluid"> <div class="row-fluid">
<div class="span4"> <div class="span4">
<div class="block"> <div class="block">
<div class="account-image">@avatar(account.userName, 200)</div> <div class="account-image">@avatar(account.userName, 270)</div>
<div class="account-fullname">@account.fullName</div> <div class="account-fullname">@account.fullName</div>
<div class="account-username">@account.userName</div> <div class="account-username">@account.userName</div>
</div> </div>

View File

@@ -133,6 +133,13 @@
<span id="error-ldap_userNameAttribute" class="error"></span> <span id="error-ldap_userNameAttribute" class="error"></span>
</div> </div>
</div> </div>
<div class="control-group">
<label class="control-label" for="ldapAdditionalFilterCondition">Additional filter condition</label>
<div class="controls">
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" value="@settings.ldap.map(_.additionalFilterCondition)"/>
<span id="error-ldap_additionalFilterCondition" class="error"></span>
</div>
</div>
<div class="control-group"> <div class="control-group">
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label> <label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
<div class="controls"> <div class="controls">

View File

@@ -59,7 +59,7 @@ $(function(){
}); });
$('#addMember').click(function(){ $('#addMember').click(function(){
$('#error-memberName').text(''); $('#error-members').text('');
var userName = $('#memberName').val(); var userName = $('#memberName').val();
// check empty // check empty
@@ -72,18 +72,18 @@ $(function(){
return $(this).data('name') == userName; return $(this).data('name') == userName;
}).length > 0; }).length > 0;
if(exists){ if(exists){
$('#error-memberName').text('User has been already added.'); $('#error-members').text('User has been already added.');
return false; return false;
} }
// check existence // check existence
$.post('@path/admin/users/_usercheck', { $.post('@path/_user/existence', {
'userName': userName 'userName': userName
}, function(data, status){ }, function(data, status){
if(data == 'true'){ if(data == 'true'){
addMemberHTML(userName, false); addMemberHTML(userName, false);
} else { } else {
$('#error-memberName').text('User does not exist.'); $('#error-members').text('User does not exist.');
} }
}); });
}); });

View File

@@ -22,7 +22,7 @@
case "fork" => simpleActivity(activity, "activity-fork.png") case "fork" => simpleActivity(activity, "activity-fork.png")
case "push" => customActivity(activity, "activity-commit.png"){ case "push" => customActivity(activity, "activity-commit.png"){
<div class="small activity-message"> <div class="small activity-message">
{activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) => {activity.additionalInfo.get.split("\n").reverse.filter(_ matches "[0-9a-z]{40}:.*").take(4).zipWithIndex.map{ case (commit, i) =>
if(i == 3){ if(i == 3){
<div>...</div> <div>...</div>
} else { } else {

View File

@@ -24,6 +24,7 @@
}); });
var title = $('#@id').attr('title'); var title = $('#@id').attr('title');
$('#@id').removeAttr('title') $('#@id').removeAttr('title')
clip.htmlBridge = "#global-zeroclipboard-html-bridge";
clip.on('complete', function(client, args) { clip.on('complete', function(client, args) {
$(clip.htmlBridge).attr('title', 'copied!').tooltip('fixTitle').tooltip('show'); $(clip.htmlBridge).attr('title', 'copied!').tooltip('fixTitle').tooltip('show');
$(clip.htmlBridge).attr('title', title).tooltip('fixTitle'); $(clip.htmlBridge).attr('title', title).tooltip('fixTitle');

View File

@@ -11,11 +11,13 @@
<script> <script>
$(function(){ $(function(){
var callback = function(data){ var callback = function(data){
$('#update-comment-@commentId, #cancel-comment-@commentId').removeAttr('disabled');
$('#commentContent-@commentId').empty().html(data.content); $('#commentContent-@commentId').empty().html(data.content);
prettyPrint(); prettyPrint();
}; };
$('#update-comment-@commentId').click(function(){ $('#update-comment-@commentId').click(function(){
$('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
$.ajax({ $.ajax({
url: '@path/@owner/@repository/issue_comments/edit/@commentId', url: '@path/@owner/@repository/issue_comments/edit/@commentId',
type: 'POST', type: 'POST',
@@ -26,11 +28,13 @@ $(function(){
}).done( }).done(
callback callback
).fail(function(req) { ).fail(function(req) {
$('#update-comment-@commentId, #cancel-comment-@commentId').removeAttr('disabled');
$('#error-edit-content-@commentId').text($.parseJSON(req.responseText).content); $('#error-edit-content-@commentId').text($.parseJSON(req.responseText).content);
}); });
}); });
$('#cancel-comment-@commentId').click(function(){ $('#cancel-comment-@commentId').click(function(){
$('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
$.get('@path/@owner/@repository/issue_comments/_data/@commentId', callback); $.get('@path/@owner/@repository/issue_comments/_data/@commentId', callback);
return false; return false;
}); });

View File

@@ -14,11 +14,13 @@ $(function(){
$('#edit-content').elastic(); $('#edit-content').elastic();
var callback = function(data){ var callback = function(data){
$('#update, #cancel').removeAttr('disabled');
$('#issueTitle').empty().text(data.title); $('#issueTitle').empty().text(data.title);
$('#issueContent').empty().html(data.content); $('#issueContent').empty().html(data.content);
}; };
$('#update').click(function(){ $('#update').click(function(){
$('#update, #cancel').attr('disabled', 'disabled');
$.ajax({ $.ajax({
url: '@path/@owner/@repository/issues/edit/@issueId', url: '@path/@owner/@repository/issues/edit/@issueId',
type: 'POST', type: 'POST',
@@ -29,11 +31,13 @@ $(function(){
}).done( }).done(
callback callback
).fail(function(req) { ).fail(function(req) {
$('#update, #cancel').removeAttr('disabled');
$('#error-edit-title').text($.parseJSON(req.responseText).title); $('#error-edit-title').text($.parseJSON(req.responseText).title);
}); });
}); });
$('#cancel').click(function(){ $('#cancel').click(function(){
$('#update, #cancel').attr('disabled', 'disabled');
$.get('@path/@owner/@repository/issues/_data/@issueId', callback); $.get('@path/@owner/@repository/issues/_data/@issueId', callback);
return false; return false;
}); });

View File

@@ -87,6 +87,9 @@
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download ZIP</a> <a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download ZIP</a>
</div> </div>
<div style="margin-top: 10px;">
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.tar.gz" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download TAR.GZ</a>
</div>
} }
} }
</div> </div>

View File

@@ -6,13 +6,13 @@
<table class="table table-file-list" style="border: 1px solid silver;"> <table class="table table-file-list" style="border: 1px solid silver;">
@commits.map { day => @commits.map { day =>
<tr> <tr>
<th colspan="3" class="box-header" style="font-weight: normal;">@date(day.head.time)</th> <th colspan="3" class="box-header" style="font-weight: normal;">@date(day.head.commitTime)</th>
</tr> </tr>
@day.map { commit => @day.map { commit =>
<tr> <tr>
<td style="width: 20%;"> <td style="width: 20%;">
@avatar(commit, 20) @avatar(commit, 20)
@user(commit.committer, commit.mailAddress, "username") @user(commit.authorName, commit.authorEmailAddress, "username")
</td> </td>
<td>@commit.shortMessage</td> <td>@commit.shortMessage</td>
<td style="width: 10%; text-align: right;"> <td style="width: 10%; text-align: right;">

View File

@@ -42,7 +42,7 @@
pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){ pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){
<div class="box issue-comment-box" style="background-color: #d0eeff;"> <div class="box issue-comment-box" style="background-color: #d0eeff;">
<div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;"> <div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;">
<a href="@url(repository)/pull/@issue.issueId/delete/@pullreq.requestBranch" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a> <a href="@url(repository)/pull/@issue.issueId/delete/@encodeRefName(pullreq.requestBranch)" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a>
<div> <div>
<span class="strong">Pull request successfully merged and closed</span> <span class="strong">Pull request successfully merged and closed</span>
</div> </div>

View File

@@ -42,7 +42,7 @@
<p> <p>
<span class="strong">Step 1:</span> Check out a new branch to test the changes — run this from your project directory <span class="strong">Step 1:</span> Check out a new branch to test the changes — run this from your project directory
</p> </p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.requestBranch}"){ command => @defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-1", command){ @helper.html.copy("merge-command-copy-1", command){
<pre style="width: 500px; float: left;">@command</pre> <pre style="width: 500px; float: left;">@command</pre>
} }
@@ -62,7 +62,7 @@
<p> <p>
<span class="strong">Step 3:</span> Merge the changes and update the server <span class="strong">Step 3:</span> Merge the changes and update the server
</p> </p>
@defining(s"git checkout master\ngit merge ${pullreq.requestUserName}-${pullreq.branch}\ngit push origin ${pullreq.branch}"){ command => @defining(s"git checkout master\ngit merge ${pullreq.requestUserName}-${pullreq.requestBranch}\ngit push origin ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-3", command){ @helper.html.copy("merge-command-copy-3", command){
<pre style="width: 500px; float: left;">@command</pre> <pre style="width: 500px; float: left;">@command</pre>
} }

View File

@@ -33,8 +33,8 @@
<th style="font-weight: normal;"> <th style="font-weight: normal;">
<div class="pull-left"> <div class="pull-left">
@avatar(latestCommit, 20) @avatar(latestCommit, 20)
@user(latestCommit.committer, latestCommit.mailAddress, "username strong") @user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
<span class="muted">@datetime(latestCommit.time)</span> <span class="muted">@datetime(latestCommit.commitTime)</span>
<a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a> <a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
</div> </div>
<div class="btn-group pull-right"> <div class="btn-group pull-right">

View File

@@ -31,7 +31,10 @@
<a href="@url(repository)/compare/@{encodeRefName(repository.repository.defaultBranch)}...@{encodeRefName(branchName)}">to @{repository.repository.defaultBranch}</a> <a href="@url(repository)/compare/@{encodeRefName(repository.repository.defaultBranch)}...@{encodeRefName(branchName)}">to @{repository.repository.defaultBranch}</a>
} }
</td> </td>
<td><a href="@url(repository)/archive/@{encodeRefName(branchName)}.zip">ZIP</a></td> <td>
<a href="@url(repository)/archive/@{encodeRefName(branchName)}.zip">ZIP</a>
<a href="@url(repository)/archive/@{encodeRefName(branchName)}.tar.gz">TAR.GZ</a>
</td>
</tr> </tr>
} }
</table> </table>

View File

@@ -42,9 +42,6 @@
</tr> </tr>
<tr> <tr>
<td> <td>
@avatar(commit, 20)
@user(commit.committer, commit.mailAddress, "username strong")
<span class="muted">@datetime(commit.time)</span>
<div class="pull-right monospace small" style="text-align: right;"> <div class="pull-right monospace small" style="text-align: right;">
<div> <div>
@if(commit.parents.size == 0){ @if(commit.parents.size == 0){
@@ -66,6 +63,21 @@
</div> </div>
} }
</div> </div>
<div class="author-info">
<div class="author">
@avatar(commit, 20)
<span>@user(commit.authorName, commit.authorEmailAddress, "username strong")</span>
<span class="muted">authored on @datetime(commit.authorTime)</span>
</div>
@if(commit.isDifferentFromAuthor) {
<div class="committer">
<span class="icon-arrow-right"></span>
<span>@user(commit.committerName, commit.committerEmailAddress, "username strong")</span>
<span class="muted"> committed on @datetime(commit.commitTime)</span>
</div>
}
</div>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -36,7 +36,7 @@
@commits.map { day => @commits.map { day =>
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<th>@date(day.head.time)</th> <th>@date(day.head.commitTime)</th>
</tr> </tr>
@day.map { commit => @day.map { commit =>
<tr> <tr>
@@ -57,8 +57,13 @@
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre> <pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
} }
<div class="small"> <div class="small">
@user(commit.committer, commit.mailAddress, "username") @user(commit.authorName, commit.authorEmailAddress, "username")
<span class="muted">@datetime(commit.time)</span> <span class="muted">authored @datetime(commit.authorTime)</span>
@if(commit.isDifferentFromAuthor) {
<span class="icon-arrow-right" style="margin-top : -2px ;"></span>
@user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @datetime(commit.authorTime)</span>
}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -60,6 +60,7 @@
} }
<input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/> <input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
<input type="hidden" id="charset" name="charset" value="@content.charset"/> <input type="hidden" id="charset" name="charset" value="@content.charset"/>
<input type="hidden" id="lineSeparator" name="lineSeparator" value="@content.lineSeparator"/>
<input type="hidden" id="content" name="content" value=""/> <input type="hidden" id="content" name="content" value=""/>
<input type="hidden" id="initial" value="@content.content"/> <input type="hidden" id="initial" value="@content.content"/>
</div> </div>

View File

@@ -40,12 +40,23 @@
<tr> <tr>
<td colspan="4" class="latest-commit"> <td colspan="4" class="latest-commit">
<div> <div>
@avatar(latestCommit, 20)
@user(latestCommit.committer, latestCommit.mailAddress, "username strong")
<span class="muted">@datetime(latestCommit.time)</span>
<div class="pull-right align-right monospace" style="line-height: 18px;"> <div class="pull-right align-right monospace" style="line-height: 18px;">
<a href="@url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> @latestCommit.id.substring(0, 10)</a> <a href="@url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> @latestCommit.id.substring(0, 10)</a>
</div> </div>
<div class="author-info">
<div class="author">
@avatar(latestCommit, 20)
<span>@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")</span>
<span class="muted"> authored on @datetime(latestCommit.authorTime)</span>
</div>
@if(latestCommit.isDifferentFromAuthor) {
<div class="committer">
<span class="icon-arrow-right"></span>
<span>@user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong")</span>
<span class="muted"> committed on @datetime(latestCommit.commitTime)</span>
</div>
}
</div>
</div> </div>
</td> </td>
</tr> </tr>
@@ -83,7 +94,7 @@
</td> </td>
<td class="mute"> <td class="mute">
<a href="@url(repository)/commit/@file.commitId" class="commit-message">@link(file.message, repository)</a> <a href="@url(repository)/commit/@file.commitId" class="commit-message">@link(file.message, repository)</a>
[@user(file.committer, file.mailAddress)] [@user(file.author, file.mailAddress)]
</td> </td>
<td style="text-align: right;">@datetime(file.time)</td> <td style="text-align: right;">@datetime(file.time)</td>
</tr> </tr>

View File

@@ -16,7 +16,10 @@
<td><a href="@url(repository)/tree/@encodeRefName(tag.name)">@tag.name</a></td> <td><a href="@url(repository)/tree/@encodeRefName(tag.name)">@tag.name</a></td>
<td>@datetime(tag.time)</td> <td>@datetime(tag.time)</td>
<td class="monospace"><a href="@url(repository)/commit/@tag.id">@tag.id.substring(0, 10)</a></td> <td class="monospace"><a href="@url(repository)/commit/@tag.id">@tag.id.substring(0, 10)</a></td>
<td><a href="@url(repository)/archive/@{encodeRefName(tag.name)}.zip">ZIP</a></td> <td>
<a href="@url(repository)/archive/@{encodeRefName(tag.name)}.zip">ZIP</a>
<a href="@url(repository)/archive/@{encodeRefName(tag.name)}.tar.gz">TAR.GZ</a>
</td>
</tr> </tr>
} }
</table> </table>

View File

@@ -34,9 +34,9 @@
@commits.map { commit => @commits.map { commit =>
<tr> <tr>
<td width="0%"><input type="checkbox" name="commitId" value="@commit.id"></td> <td width="0%"><input type="checkbox" name="commitId" value="@commit.id"></td>
<td>@avatar(commit, 20)&nbsp;@user(commit.committer, commit.mailAddress)</td> <td>@avatar(commit, 20)&nbsp;@user(commit.authorName, commit.authorEmailAddress)</td>
<td width="80%"> <td width="80%">
<span class="muted">@datetime(commit.time):</span>&nbsp;@commit.shortMessage <span class="muted">@datetime(commit.authorTime):</span>&nbsp;@commit.shortMessage
</td> </td>
</tr> </tr>
} }

View File

@@ -52,7 +52,7 @@
</div> </div>
} }
</div> </div>
<div style="margin-right: 220px;"> <div style="width: 650px;" class="pull-left">
<div class="markdown-body"> <div class="markdown-body">
@markdown(page.content, repository, true, false) @markdown(page.content, repository, true, false)
</div> </div>

View File

@@ -392,12 +392,14 @@ div.signin-form {
/* Account page */ /* Account page */
/****************************************************************************/ /****************************************************************************/
.account-fullname { .account-fullname {
font-size: 140%; margin-top: 20px;
font-size: 180%;
font-weight: bold; font-weight: bold;
} }
.account-username { .account-username {
font-size: 120%; margin-top: 10px;
font-size: 140%;
color: #888888; color: #888888;
font-weight: bold; font-weight: bold;
} }
@@ -822,6 +824,15 @@ a.absent {
color: #c00; color: #c00;
} }
/****************************************************************************/
/* Commit */
/****************************************************************************/
div.author-info div.committer {
display: block;
margin-left: 25px;
font-size: 12px;
}
/****************************************************************************/ /****************************************************************************/
/* Diff */ /* Diff */
/****************************************************************************/ /****************************************************************************/
@@ -879,7 +890,6 @@ div.markdown-body h1 {
div.markdown-body h2 { div.markdown-body h2 {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
font-size: 2em; font-size: 2em;
margin-top: 30px;
} }
div.markdown-body h3 { div.markdown-body h3 {

View File

@@ -12,12 +12,14 @@ $(function(){
function validate(e){ function validate(e){
var form = $(e.target); var form = $(e.target);
$(form).find('[type=submit]').attr('disabled', 'disabled')
if(form.data('validated') == true){ if(form.data('validated') == true){
return true; return true;
} }
$.post(form.attr('action') + '/validate', $(e.target).serialize(), function(data){ $.post(form.attr('action') + '/validate', $(e.target).serialize(), function(data){
$(form).find('[type=submit]').removeAttr('disabled')
// clear all error messages // clear all error messages
$('.error').text(''); $('.error').text('');

File diff suppressed because one or more lines are too long

View File

@@ -1,79 +1,79 @@
//package service package service
//
//import org.specs2.mutable.Specification import org.specs2.mutable.Specification
//import java.util.Date import java.util.Date
//import model.GroupMember import model.GroupMember
//
//class AccountServiceSpec extends Specification with ServiceSpecBase { class AccountServiceSpec extends Specification with ServiceSpecBase {
//
// "AccountService" should { "AccountService" should {
// val RootMailAddress = "root@localhost" val RootMailAddress = "root@localhost"
//
// "getAllUsers" in { withTestDB{ "getAllUsers" in { withTestDB { implicit session =>
// AccountService.getAllUsers() must be like{ AccountService.getAllUsers() must be like{
// case List(model.Account("root", "root", RootMailAddress, _, true, _, _, _, None, None, false, false)) => ok case List(model.Account("root", "root", RootMailAddress, _, true, _, _, _, None, None, false, false)) => ok
// } }
// }} }}
//
// "getAccountByUserName" in { withTestDB{ "getAccountByUserName" in { withTestDB { implicit session =>
// AccountService.getAccountByUserName("root") must beSome.like{ AccountService.getAccountByUserName("root") must beSome.like {
// case user => user.userName must_== "root" case user => user.userName must_== "root"
// } }
//
// AccountService.getAccountByUserName("invalid user name") must beNone AccountService.getAccountByUserName("invalid user name") must beNone
// }} }}
//
// "getAccountByMailAddress" in { withTestDB{ "getAccountByMailAddress" in { withTestDB { implicit session =>
// AccountService.getAccountByMailAddress(RootMailAddress) must beSome AccountService.getAccountByMailAddress(RootMailAddress) must beSome
// }} }}
//
// "updateLastLoginDate" in { withTestDB{ "updateLastLoginDate" in { withTestDB { implicit session =>
// val root = "root" val root = "root"
// def user() = def user() =
// AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists")) AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists"))
//
// user().lastLoginDate must beNone user().lastLoginDate must beNone
// val date1 = new Date val date1 = new Date
// AccountService.updateLastLoginDate(root) AccountService.updateLastLoginDate(root)
// user().lastLoginDate must beSome.like{ case date => user().lastLoginDate must beSome.like{ case date =>
// date must be_>(date1) date must be_>(date1)
// } }
// val date2 = new Date val date2 = new Date
// Thread.sleep(1000) Thread.sleep(1000)
// AccountService.updateLastLoginDate(root) AccountService.updateLastLoginDate(root)
// user().lastLoginDate must beSome.like{ case date => user().lastLoginDate must beSome.like{ case date =>
// date must be_>(date2) date must be_>(date2)
// } }
// }} }}
//
// "updateAccount" in { withTestDB{ "updateAccount" in { withTestDB { implicit session =>
// val root = "root" val root = "root"
// def user() = def user() =
// AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists")) AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists"))
//
// val newAddress = "new mail address" val newAddress = "new mail address"
// AccountService.updateAccount(user().copy(mailAddress = newAddress)) AccountService.updateAccount(user().copy(mailAddress = newAddress))
// user().mailAddress must_== newAddress user().mailAddress must_== newAddress
// }} }}
//
// "group" in { withTestDB { "group" in { withTestDB { implicit session =>
// val group1 = "group1" val group1 = "group1"
// val user1 = "root" val user1 = "root"
// AccountService.createGroup(group1, None) AccountService.createGroup(group1, None)
//
// AccountService.getGroupMembers(group1) must_== Nil AccountService.getGroupMembers(group1) must_== Nil
// AccountService.getGroupsByUserName(user1) must_== Nil AccountService.getGroupsByUserName(user1) must_== Nil
//
// AccountService.updateGroupMembers(group1, List((user1, true))) AccountService.updateGroupMembers(group1, List((user1, true)))
//
// AccountService.getGroupMembers(group1) must_== List(GroupMember(group1, user1, true)) AccountService.getGroupMembers(group1) must_== List(GroupMember(group1, user1, true))
// AccountService.getGroupsByUserName(user1) must_== List(group1) AccountService.getGroupsByUserName(user1) must_== List(group1)
//
// AccountService.updateGroupMembers(group1, Nil) AccountService.updateGroupMembers(group1, Nil)
//
// AccountService.getGroupMembers(group1) must_== Nil AccountService.getGroupMembers(group1) must_== Nil
// AccountService.getGroupsByUserName(user1) must_== Nil AccountService.getGroupsByUserName(user1) must_== Nil
// }} }}
// } }
//} }
//

View File

@@ -1,25 +1,26 @@
//package service package service
//
//import scala.slick.session.Database import model.Profile._
//import util.ControlUtil._ import profile.simple._
//import java.sql.DriverManager import util.ControlUtil._
//import org.apache.commons.io.FileUtils import java.sql.DriverManager
//import scala.util.Random import org.apache.commons.io.FileUtils
//import java.io.File import scala.util.Random
// import java.io.File
//trait ServiceSpecBase {
// trait ServiceSpecBase {
// def withTestDB[A](action: => A): A = {
// util.FileUtil.withTmpDir(new File(FileUtils.getTempDirectory(), Random.alphanumeric.take(10).mkString)){ dir => def withTestDB[A](action: (Session) => A): A = {
// val (url, user, pass) = (s"jdbc:h2:${dir}", "sa", "sa") util.FileUtil.withTmpDir(new File(FileUtils.getTempDirectory(), Random.alphanumeric.take(10).mkString)){ dir =>
// org.h2.Driver.load() val (url, user, pass) = (s"jdbc:h2:${dir}", "sa", "sa")
// using(DriverManager.getConnection(url, user, pass)){ conn => org.h2.Driver.load()
// servlet.AutoUpdate.versions.reverse.foreach(_.update(conn)) using(DriverManager.getConnection(url, user, pass)){ conn =>
// } servlet.AutoUpdate.versions.reverse.foreach(_.update(conn))
// Database.forURL(url, user, pass).withSession { }
// action Database.forURL(url, user, pass).withSession { session =>
// } action(session)
// } }
// } }
// }
//}
}