(refs #310)Convert all CRLF to LF

This commit is contained in:
Naoki Takezoe
2014-06-29 04:16:37 +09:00
parent 1c99b57709
commit fd09058a7d
37 changed files with 93251 additions and 57107 deletions

View File

@@ -1,135 +1,135 @@
CREATE TABLE ACCOUNT( CREATE TABLE ACCOUNT(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
MAIL_ADDRESS VARCHAR(100) NOT NULL, MAIL_ADDRESS VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(40) NOT NULL, PASSWORD VARCHAR(40) NOT NULL,
ADMINISTRATOR BOOLEAN NOT NULL, ADMINISTRATOR BOOLEAN NOT NULL,
URL VARCHAR(200), URL VARCHAR(200),
REGISTERED_DATE TIMESTAMP NOT NULL, REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL, UPDATED_DATE TIMESTAMP NOT NULL,
LAST_LOGIN_DATE TIMESTAMP LAST_LOGIN_DATE TIMESTAMP
); );
CREATE TABLE REPOSITORY( CREATE TABLE REPOSITORY(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
PRIVATE BOOLEAN NOT NULL, PRIVATE BOOLEAN NOT NULL,
DESCRIPTION TEXT, DESCRIPTION TEXT,
DEFAULT_BRANCH VARCHAR(100), DEFAULT_BRANCH VARCHAR(100),
REGISTERED_DATE TIMESTAMP NOT NULL, REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL, UPDATED_DATE TIMESTAMP NOT NULL,
LAST_ACTIVITY_DATE TIMESTAMP NOT NULL LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
); );
CREATE TABLE COLLABORATOR( CREATE TABLE COLLABORATOR(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
COLLABORATOR_NAME VARCHAR(100) NOT NULL COLLABORATOR_NAME VARCHAR(100) NOT NULL
); );
CREATE TABLE ISSUE( CREATE TABLE ISSUE(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL, ISSUE_ID INT NOT NULL,
OPENED_USER_NAME VARCHAR(100) NOT NULL, OPENED_USER_NAME VARCHAR(100) NOT NULL,
MILESTONE_ID INT, MILESTONE_ID INT,
ASSIGNED_USER_NAME VARCHAR(100), ASSIGNED_USER_NAME VARCHAR(100),
TITLE TEXT NOT NULL, TITLE TEXT NOT NULL,
CONTENT TEXT, CONTENT TEXT,
CLOSED BOOLEAN NOT NULL, CLOSED BOOLEAN NOT NULL,
REGISTERED_DATE TIMESTAMP NOT NULL, REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL UPDATED_DATE TIMESTAMP NOT NULL
); );
CREATE TABLE ISSUE_ID( CREATE TABLE ISSUE_ID(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL ISSUE_ID INT NOT NULL
); );
CREATE TABLE ISSUE_COMMENT( CREATE TABLE ISSUE_COMMENT(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL, ISSUE_ID INT NOT NULL,
COMMENT_ID INT AUTO_INCREMENT, COMMENT_ID INT AUTO_INCREMENT,
ACTION VARCHAR(10), ACTION VARCHAR(10),
COMMENTED_USER_NAME VARCHAR(100) NOT NULL, COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
CONTENT TEXT NOT NULL, CONTENT TEXT NOT NULL,
REGISTERED_DATE TIMESTAMP NOT NULL, REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL UPDATED_DATE TIMESTAMP NOT NULL
); );
CREATE TABLE LABEL( CREATE TABLE LABEL(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
LABEL_ID INT AUTO_INCREMENT, LABEL_ID INT AUTO_INCREMENT,
LABEL_NAME VARCHAR(100) NOT NULL, LABEL_NAME VARCHAR(100) NOT NULL,
COLOR CHAR(6) NOT NULL COLOR CHAR(6) NOT NULL
); );
CREATE TABLE ISSUE_LABEL( CREATE TABLE ISSUE_LABEL(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL, ISSUE_ID INT NOT NULL,
LABEL_ID INT NOT NULL LABEL_ID INT NOT NULL
); );
CREATE TABLE MILESTONE( CREATE TABLE MILESTONE(
USER_NAME VARCHAR(100) NOT NULL, USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL, REPOSITORY_NAME VARCHAR(100) NOT NULL,
MILESTONE_ID INT AUTO_INCREMENT, MILESTONE_ID INT AUTO_INCREMENT,
TITLE VARCHAR(100) NOT NULL, TITLE VARCHAR(100) NOT NULL,
DESCRIPTION TEXT, DESCRIPTION TEXT,
DUE_DATE TIMESTAMP, DUE_DATE TIMESTAMP,
CLOSED_DATE TIMESTAMP CLOSED_DATE TIMESTAMP
); );
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME); ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS); ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME); ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME); ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME); ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME); ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME); ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME); ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME); ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME); ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID); ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME); ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME); ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID); ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID); ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID); ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID); ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME); ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID); ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID); ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID); ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME); ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
INSERT INTO ACCOUNT ( INSERT INTO ACCOUNT (
USER_NAME, USER_NAME,
MAIL_ADDRESS, MAIL_ADDRESS,
PASSWORD, PASSWORD,
ADMINISTRATOR, ADMINISTRATOR,
URL, URL,
REGISTERED_DATE, REGISTERED_DATE,
UPDATED_DATE, UPDATED_DATE,
LAST_LOGIN_DATE LAST_LOGIN_DATE
) VALUES ( ) VALUES (
'root', 'root',
'root@localhost', 'root@localhost',
'dc76e9f0c0006e8f919e0c515c66dbba3982f785', 'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
true, true,
'https://github.com/takezoe/gitbucket', 'https://github.com/takezoe/gitbucket',
SYSDATE, SYSDATE,
SYSDATE, SYSDATE,
NULL NULL
); );

View File

@@ -1,109 +1,109 @@
package app package app
import service._ import service._
import util.{UsersAuthenticator, Keys} import util.{UsersAuthenticator, Keys}
import util.Implicits._ import util.Implicits._
class DashboardController extends DashboardControllerBase class DashboardController extends DashboardControllerBase
with IssuesService with PullRequestService with RepositoryService with AccountService with IssuesService with PullRequestService with RepositoryService with AccountService
with UsersAuthenticator with UsersAuthenticator
trait DashboardControllerBase extends ControllerBase { trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with UsersAuthenticator => self: IssuesService with PullRequestService with RepositoryService with UsersAuthenticator =>
get("/dashboard/issues/repos")(usersOnly { get("/dashboard/issues/repos")(usersOnly {
searchIssues("all") searchIssues("all")
}) })
get("/dashboard/issues/assigned")(usersOnly { get("/dashboard/issues/assigned")(usersOnly {
searchIssues("assigned") searchIssues("assigned")
}) })
get("/dashboard/issues/created_by")(usersOnly { get("/dashboard/issues/created_by")(usersOnly {
searchIssues("created_by") searchIssues("created_by")
}) })
get("/dashboard/pulls")(usersOnly { get("/dashboard/pulls")(usersOnly {
searchPullRequests("created_by", None) searchPullRequests("created_by", None)
}) })
get("/dashboard/pulls/owned")(usersOnly { get("/dashboard/pulls/owned")(usersOnly {
searchPullRequests("created_by", None) searchPullRequests("created_by", None)
}) })
get("/dashboard/pulls/public")(usersOnly { get("/dashboard/pulls/public")(usersOnly {
searchPullRequests("not_created_by", None) searchPullRequests("not_created_by", None)
}) })
get("/dashboard/pulls/for/:owner/:repository")(usersOnly { get("/dashboard/pulls/for/:owner/:repository")(usersOnly {
searchPullRequests("all", Some(params("owner") + "/" + params("repository"))) searchPullRequests("all", Some(params("owner") + "/" + params("repository")))
}) })
private def searchIssues(filter: String) = { private def searchIssues(filter: String) = {
import IssuesService._ import IssuesService._
// condition // condition
val condition = session.putAndGet(Keys.Session.DashboardIssues, val condition = session.putAndGet(Keys.Session.DashboardIssues,
if(request.hasQueryString) IssueSearchCondition(request) if(request.hasQueryString) IssueSearchCondition(request)
else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition()) else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition())
) )
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 repositories = getUserRepositories(userName, context.baseUrl).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, repositories: _*),
page, page,
countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*), countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*),
countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*), countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*),
condition), condition),
countIssue(condition, Map.empty, false, repositories: _*), countIssue(condition, Map.empty, false, repositories: _*),
countIssue(condition, Map("assigned" -> userName), false, repositories: _*), countIssue(condition, Map("assigned" -> userName), false, repositories: _*),
countIssue(condition, Map("created_by" -> userName), false, repositories: _*), countIssue(condition, Map("created_by" -> userName), false, repositories: _*),
countIssueGroupByRepository(condition, filterUser, false, repositories: _*), countIssueGroupByRepository(condition, filterUser, false, repositories: _*),
condition, condition,
filter) filter)
} }
private def searchPullRequests(filter: String, repository: Option[String]) = { private def searchPullRequests(filter: String, repository: Option[String]) = {
import IssuesService._ import IssuesService._
import PullRequestService._ import PullRequestService._
// condition // condition
val condition = session.putAndGet(Keys.Session.DashboardPulls, { val condition = session.putAndGet(Keys.Session.DashboardPulls, {
if(request.hasQueryString) IssueSearchCondition(request) if(request.hasQueryString) IssueSearchCondition(request)
else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition()) else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition())
}.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 repositories = getUserRepositories(userName, context.baseUrl).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, repositories: _*)
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, repositories: _*),
page, page,
countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*), countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*),
countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*), countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*),
condition, condition,
None, None,
false), false),
getPullRequestCountGroupByUser(condition.state == "closed", None, None), getPullRequestCountGroupByUser(condition.state == "closed", None, None),
getRepositoryNamesOfUser(userName).map { RepoName => getRepositoryNamesOfUser(userName).map { RepoName =>
(userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0)) (userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0))
}.sortBy(_._3).reverse, }.sortBy(_._3).reverse,
condition, condition,
filter) filter)
} }
} }

View File

@@ -1,85 +1,85 @@
package app package app
import util._ import util._
import service._ import service._
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
class IndexController extends IndexControllerBase class IndexController extends IndexControllerBase
with RepositoryService with ActivityService with AccountService with UsersAuthenticator with RepositoryService with ActivityService with AccountService with UsersAuthenticator
trait IndexControllerBase extends ControllerBase { trait IndexControllerBase extends ControllerBase {
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator => self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
case class SignInForm(userName: String, password: String) case class SignInForm(userName: String, password: String)
val form = mapping( val form = mapping(
"userName" -> trim(label("Username", text(required))), "userName" -> trim(label("Username", text(required))),
"password" -> trim(label("Password", text(required))) "password" -> trim(label("Password", text(required)))
)(SignInForm.apply) )(SignInForm.apply)
get("/"){ get("/"){
val loginAccount = context.loginAccount val loginAccount = context.loginAccount
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)
) )
} }
get("/signin"){ get("/signin"){
val redirect = params.get("redirect") val redirect = params.get("redirect")
if(redirect.isDefined && redirect.get.startsWith("/")){ if(redirect.isDefined && redirect.get.startsWith("/")){
flash += Keys.Flash.Redirect -> redirect.get flash += Keys.Flash.Redirect -> redirect.get
} }
html.signin() html.signin()
} }
post("/signin", form){ form => post("/signin", form){ form =>
authenticate(context.settings, form.userName, form.password) match { authenticate(context.settings, form.userName, form.password) match {
case Some(account) => signin(account) case Some(account) => signin(account)
case None => redirect("/signin") case None => redirect("/signin")
} }
} }
get("/signout"){ get("/signout"){
session.invalidate session.invalidate
redirect("/") redirect("/")
} }
get("/activities.atom"){ get("/activities.atom"){
contentType = "application/atom+xml; type=feed" contentType = "application/atom+xml; type=feed"
helper.xml.feed(getRecentActivities()) helper.xml.feed(getRecentActivities())
} }
/** /**
* Set account information into HttpSession and redirect. * Set account information into HttpSession and redirect.
*/ */
private def signin(account: model.Account) = { private def signin(account: model.Account) = {
session.setAttribute(Keys.Session.LoginAccount, account) session.setAttribute(Keys.Session.LoginAccount, account)
updateLastLoginDate(account.userName) updateLastLoginDate(account.userName)
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("/")
} else { } else {
redirect(redirectUrl) redirect(redirectUrl)
} }
}.getOrElse { }.getOrElse {
redirect("/") redirect("/")
} }
} }
/** /**
* JSON API for collaborator completion. * JSON API for collaborator completion.
* *
* TODO Move to other controller? * TODO Move to other controller?
*/ */
get("/_user/proposals")(usersOnly { get("/_user/proposals")(usersOnly {
contentType = formats("json") contentType = formats("json")
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray) Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
) )
}) })
} }

View File

@@ -1,403 +1,403 @@
package app package app
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import service._ import service._
import IssuesService._ import IssuesService._
import util._ import util._
import util.Implicits._ import util.Implicits._
import util.ControlUtil._ import util.ControlUtil._
import org.scalatra.Ok import org.scalatra.Ok
import model.Issue import model.Issue
class IssuesController extends IssuesControllerBase class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
trait IssuesControllerBase extends ControllerBase { trait IssuesControllerBase extends ControllerBase {
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator => with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
case class IssueCreateForm(title: String, content: Option[String], case class IssueCreateForm(title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String]) assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
case class IssueEditForm(title: String, content: Option[String]) case class IssueEditForm(title: String, content: Option[String])
case class CommentForm(issueId: Int, content: String) case class CommentForm(issueId: Int, content: String)
case class IssueStateForm(issueId: Int, content: Option[String]) case class IssueStateForm(issueId: Int, content: Option[String])
val issueCreateForm = mapping( val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))), "title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())), "content" -> trim(optional(text())),
"assignedUserName" -> trim(optional(text())), "assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())), "milestoneId" -> trim(optional(number())),
"labelNames" -> trim(optional(text())) "labelNames" -> trim(optional(text()))
)(IssueCreateForm.apply) )(IssueCreateForm.apply)
val issueEditForm = mapping( val issueEditForm = mapping(
"title" -> trim(label("Title", text(required))), "title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())) "content" -> trim(optional(text()))
)(IssueEditForm.apply) )(IssueEditForm.apply)
val commentForm = mapping( val commentForm = mapping(
"issueId" -> label("Issue Id", number()), "issueId" -> label("Issue Id", number()),
"content" -> trim(label("Comment", text(required))) "content" -> trim(label("Comment", text(required)))
)(CommentForm.apply) )(CommentForm.apply)
val issueStateForm = mapping( val issueStateForm = mapping(
"issueId" -> label("Issue Id", number()), "issueId" -> label("Issue Id", number()),
"content" -> trim(optional(text())) "content" -> trim(optional(text()))
)(IssueStateForm.apply) )(IssueStateForm.apply)
get("/:owner/:repository/issues")(referrersOnly { get("/:owner/:repository/issues")(referrersOnly {
searchIssues("all", _) searchIssues("all", _)
}) })
get("/:owner/:repository/issues/assigned/:userName")(referrersOnly { get("/:owner/:repository/issues/assigned/:userName")(referrersOnly {
searchIssues("assigned", _) searchIssues("assigned", _)
}) })
get("/:owner/:repository/issues/created_by/:userName")(referrersOnly { get("/:owner/:repository/issues/created_by/:userName")(referrersOnly {
searchIssues("created_by", _) searchIssues("created_by", _)
}) })
get("/:owner/:repository/issues/:id")(referrersOnly { repository => get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) => defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
getIssue(owner, name, issueId) map { getIssue(owner, name, issueId) map {
issues.html.issue( issues.html.issue(
_, _,
getComments(owner, name, issueId.toInt), getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt), getIssueLabels(owner, name, issueId.toInt),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name), getMilestonesWithIssueCount(owner, name),
getLabels(owner, name), getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
repository) repository)
} getOrElse NotFound } getOrElse NotFound
} }
}) })
get("/:owner/:repository/issues/new")(readableUsersOnly { repository => get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
issues.html.create( issues.html.create(
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestones(owner, name), getMilestones(owner, name),
getLabels(owner, name), getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
repository) repository)
} }
}) })
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
val writable = hasWritePermission(owner, name, context.loginAccount) val writable = hasWritePermission(owner, name, context.loginAccount)
val userName = context.loginAccount.get.userName val userName = context.loginAccount.get.userName
// insert issue // insert issue
val issueId = createIssue(owner, name, userName, form.title, form.content, val issueId = createIssue(owner, name, userName, form.title, form.content,
if(writable) form.assignedUserName else None, if(writable) form.assignedUserName else None,
if(writable) form.milestoneId else None) if(writable) form.milestoneId else None)
// insert labels // insert labels
if(writable){ if(writable){
form.labelNames.map { value => form.labelNames.map { value =>
val labels = getLabels(owner, name) val labels = getLabels(owner, name)
value.split(",").foreach { labelName => value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label => labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(owner, name, issueId, label.labelId) registerIssueLabel(owner, name, issueId, label.labelId)
} }
} }
} }
} }
// record activity // record activity
recordCreateIssueActivity(owner, name, userName, issueId, form.title) recordCreateIssueActivity(owner, name, userName, issueId, form.title)
// extract references and create refer comment // extract references and create refer comment
getIssue(owner, name, issueId.toString).foreach { issue => getIssue(owner, name, issueId.toString).foreach { issue =>
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse("")) createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
} }
// notifications // notifications
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){ Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
} }
redirect(s"/${owner}/${name}/issues/${issueId}") redirect(s"/${owner}/${name}/issues/${issueId}")
} }
}) })
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) => ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
getIssue(owner, name, params("id")).map { issue => getIssue(owner, name, params("id")).map { issue =>
if(isEditable(owner, name, issue.openedUserName)){ if(isEditable(owner, name, issue.openedUserName)){
// update issue // update issue
updateIssue(owner, name, issue.issueId, form.title, form.content) updateIssue(owner, name, issue.issueId, form.title, form.content)
// extract references and create refer comment // extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse("")) createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized } else Unauthorized
} getOrElse NotFound } getOrElse NotFound
} }
}) })
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) => handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${ redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
} getOrElse NotFound } getOrElse NotFound
}) })
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, form.content, repository)() map { case (issue, id) => handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${ redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
} getOrElse NotFound } getOrElse NotFound
}) })
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment => getComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){ if(isEditable(owner, name, comment.commentedUserName)){
updateComment(comment.commentId, form.content) updateComment(comment.commentId, form.content)
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}") redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized } else Unauthorized
} getOrElse NotFound } getOrElse NotFound
} }
}) })
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository => ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment => getComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){ if(isEditable(owner, name, comment.commentedUserName)){
Ok(deleteComment(comment.commentId)) Ok(deleteComment(comment.commentId))
} else Unauthorized } else Unauthorized
} getOrElse NotFound } getOrElse NotFound
} }
}) })
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
getIssue(repository.owner, repository.name, params("id")) map { x => getIssue(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){ if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
params.get("dataType") collect { params.get("dataType") collect {
case t if t == "html" => issues.html.editissue( case t if t == "html" => issues.html.editissue(
x.title, x.content, x.issueId, x.userName, x.repositoryName) x.title, x.content, x.issueId, x.userName, x.repositoryName)
} getOrElse { } getOrElse {
contentType = formats("json") contentType = formats("json")
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map("title" -> x.title, Map("title" -> x.title,
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.", "content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
repository, false, true) repository, false, true)
)) ))
} }
} else Unauthorized } else Unauthorized
} getOrElse NotFound } getOrElse NotFound
}) })
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository => ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
getComment(repository.owner, repository.name, params("id")) map { x => getComment(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){ if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect { params.get("dataType") collect {
case t if t == "html" => issues.html.editcomment( case t if t == "html" => issues.html.editcomment(
x.content, x.commentId, x.userName, x.repositoryName) x.content, x.commentId, x.userName, x.repositoryName)
} getOrElse { } getOrElse {
contentType = formats("json") contentType = formats("json")
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map("content" -> view.Markdown.toHtml(x.content, Map("content" -> view.Markdown.toHtml(x.content,
repository, false, true) repository, false, true)
)) ))
} }
} else Unauthorized } else Unauthorized
} getOrElse NotFound } getOrElse NotFound
}) })
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository => ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
defining(params("id").toInt){ issueId => defining(params("id").toInt){ issueId =>
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt) registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId)) issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
} }
}) })
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository => ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
defining(params("id").toInt){ issueId => defining(params("id").toInt){ issueId =>
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt) deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId)) issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
} }
}) })
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository => ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName")) updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
Ok("updated") Ok("updated")
}) })
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository => ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId")) updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
milestoneId("milestoneId").map { milestoneId => milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name) getMilestonesWithIssueCount(repository.owner, repository.name)
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) => .find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
issues.milestones.html.progress(openCount + closeCount, closeCount, false) issues.milestones.html.progress(openCount + closeCount, closeCount, false)
} getOrElse NotFound } getOrElse NotFound
} getOrElse Ok() } getOrElse Ok()
}) })
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository => post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
defining(params.get("value")){ action => defining(params.get("value")){ action =>
executeBatch(repository) { executeBatch(repository) {
handleComment(_, None, repository)( _ => action) handleComment(_, None, repository)( _ => action)
} }
} }
}) })
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository => post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
params("value").toIntOpt.map{ labelId => params("value").toIntOpt.map{ labelId =>
executeBatch(repository) { issueId => executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse { getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
registerIssueLabel(repository.owner, repository.name, issueId, labelId) registerIssueLabel(repository.owner, repository.name, issueId, labelId)
} }
} }
} getOrElse NotFound } getOrElse NotFound
}) })
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository => post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
defining(assignedUserName("value")){ value => defining(assignedUserName("value")){ value =>
executeBatch(repository) { executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value) updateAssignedUserName(repository.owner, repository.name, _, value)
} }
} }
}) })
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository => post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
defining(milestoneId("value")){ value => defining(milestoneId("value")){ value =>
executeBatch(repository) { executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value) updateMilestoneId(repository.owner, repository.name, _, value)
} }
} }
}) })
get("/:owner/:repository/_attached/:file")(referrersOnly { repository => get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
(Directory.getAttachedDir(repository.owner, repository.name) match { (Directory.getAttachedDir(repository.owner, repository.name) match {
case dir if(dir.exists && dir.isDirectory) => case dir if(dir.exists && dir.isDirectory) =>
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file => dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
contentType = FileUtil.getMimeType(file.getName) contentType = FileUtil.getMimeType(file.getName)
file file
} }
case _ => None case _ => None
}) getOrElse NotFound }) getOrElse NotFound
}) })
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "") val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt) val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean = private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = { private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
params("checked").split(',') map(_.toInt) foreach execute params("checked").split(',') map(_.toInt) foreach execute
redirect(s"/${repository.owner}/${repository.name}/issues") redirect(s"/${repository.owner}/${repository.name}/issues")
} }
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = { private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId => StringUtil.extractIssueId(message).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){ if(getIssue(owner, repository, issueId).isDefined){
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
fromIssue.issueId + ":" + fromIssue.title, "refer") fromIssue.issueId + ":" + fromIssue.title, "refer")
} }
} }
} }
/** /**
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]] * @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/ */
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo) private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
(getAction: model.Issue => Option[String] = (getAction: model.Issue => Option[String] =
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = { p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
val userName = context.loginAccount.get.userName val userName = context.loginAccount.get.userName
getIssue(owner, name, issueId.toString) map { issue => getIssue(owner, name, issueId.toString) map { issue =>
val (action, recordActivity) = val (action, recordActivity) =
getAction(issue) getAction(issue)
.collect { .collect {
case "close" => true -> (Some("close") -> case "close" => true -> (Some("close") ->
Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _)) Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
case "reopen" => false -> (Some("reopen") -> case "reopen" => false -> (Some("reopen") ->
Some(recordReopenIssueActivity _)) Some(recordReopenIssueActivity _))
} }
.map { case (closed, t) => .map { case (closed, t) =>
updateClosed(owner, name, issueId, closed) updateClosed(owner, name, issueId, closed)
t t
} }
.getOrElse(None -> None) .getOrElse(None -> None)
val commentId = content val commentId = content
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") ) .map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
.getOrElse ( action.get.capitalize -> action.get ) .getOrElse ( action.get.capitalize -> action.get )
match { match {
case (content, action) => createComment(owner, name, userName, issueId, content, action) case (content, action) => createComment(owner, name, userName, issueId, content, action)
} }
// record activity // record activity
content foreach { content foreach {
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _) (if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
(owner, name, userName, issueId, _) (owner, name, userName, issueId, _)
} }
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) ) recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
// extract references and create refer comment // extract references and create refer comment
content.map { content => content.map { content =>
createReferComment(owner, name, issue, content) createReferComment(owner, name, issue, content)
} }
// notifications // notifications
Notifier() match { Notifier() match {
case f => case f =>
content foreach { content foreach {
f.toNotify(repository, issueId, _){ f.toNotify(repository, issueId, _){
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${ Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}") if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
} }
} }
action foreach { action foreach {
f.toNotify(repository, issueId, _){ f.toNotify(repository, issueId, _){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
} }
} }
} }
issue -> commentId issue -> commentId
} }
} }
} }
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = { private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name){ case (owner, repoName) => defining(repository.owner, repository.name){ case (owner, repoName) =>
val filterUser = Map(filter -> params.getOrElse("userName", "")) val filterUser = Map(filter -> params.getOrElse("userName", ""))
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
val sessionKey = Keys.Session.Issues(owner, repoName) val sessionKey = Keys.Session.Issues(owner, repoName)
// retrieve search condition // retrieve search condition
val condition = session.putAndGet(sessionKey, val condition = session.putAndGet(sessionKey,
if(request.hasQueryString) IssueSearchCondition(request) if(request.hasQueryString) IssueSearchCondition(request)
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition()) else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
) )
issues.html.list( issues.html.list(
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page, page,
(getCollaborators(owner, repoName) :+ owner).sorted, (getCollaborators(owner, repoName) :+ owner).sorted,
getMilestones(owner, repoName), getMilestones(owner, repoName),
getLabels(owner, repoName), getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName), countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName), countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
countIssue(condition, Map.empty, false, owner -> repoName), countIssue(condition, Map.empty, false, owner -> repoName),
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)), context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)), context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
countIssueGroupByLabels(owner, repoName, condition, filterUser), countIssueGroupByLabels(owner, repoName, condition, filterUser),
condition, condition,
filter, filter,
repository, repository,
hasWritePermission(owner, repoName, context.loginAccount)) hasWritePermission(owner, repoName, context.loginAccount))
} }
} }
} }

View File

@@ -1,269 +1,269 @@
package app package app
import service._ import service._
import util.Directory._ import util.Directory._
import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator} import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import service.WebHookService.WebHookPayload import service.WebHookService.WebHookPayload
import util.JGitUtil.CommitInfo import util.JGitUtil.CommitInfo
import util.ControlUtil._ import util.ControlUtil._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
class RepositorySettingsController extends RepositorySettingsControllerBase class RepositorySettingsController extends RepositorySettingsControllerBase
with RepositoryService with AccountService with WebHookService with RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator with OwnerAuthenticator with UsersAuthenticator
trait RepositorySettingsControllerBase extends ControllerBase { trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with WebHookService self: RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator => with OwnerAuthenticator with UsersAuthenticator =>
// for repository options // for repository options
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean) case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
val optionsForm = mapping( val optionsForm = mapping(
"repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))), "repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
"description" -> trim(label("Description" , optional(text()))), "description" -> trim(label("Description" , optional(text()))),
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))), "defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
"isPrivate" -> trim(label("Repository Type", boolean())) "isPrivate" -> trim(label("Repository Type", boolean()))
)(OptionsForm.apply) )(OptionsForm.apply)
// for collaborator addition // for collaborator addition
case class CollaboratorForm(userName: String) case class CollaboratorForm(userName: String)
val collaboratorForm = mapping( val collaboratorForm = mapping(
"userName" -> trim(label("Username", text(required, collaborator))) "userName" -> trim(label("Username", text(required, collaborator)))
)(CollaboratorForm.apply) )(CollaboratorForm.apply)
// for web hook url addition // for web hook url addition
case class WebHookForm(url: String) case class WebHookForm(url: String)
val webHookForm = mapping( val webHookForm = mapping(
"url" -> trim(label("url", text(required, webHook))) "url" -> trim(label("url", text(required, webHook)))
)(WebHookForm.apply) )(WebHookForm.apply)
// for transfer ownership // for transfer ownership
case class TransferOwnerShipForm(newOwner: String) case class TransferOwnerShipForm(newOwner: String)
val transferForm = mapping( val transferForm = mapping(
"newOwner" -> trim(label("New owner", text(required, transferUser))) "newOwner" -> trim(label("New owner", text(required, transferUser)))
)(TransferOwnerShipForm.apply) )(TransferOwnerShipForm.apply)
/** /**
* Redirect to the Options page. * Redirect to the Options page.
*/ */
get("/:owner/:repository/settings")(ownerOnly { repository => get("/:owner/:repository/settings")(ownerOnly { repository =>
redirect(s"/${repository.owner}/${repository.name}/settings/options") redirect(s"/${repository.owner}/${repository.name}/settings/options")
}) })
/** /**
* Display the Options page. * Display the Options page.
*/ */
get("/:owner/:repository/settings/options")(ownerOnly { get("/:owner/:repository/settings/options")(ownerOnly {
settings.html.options(_, flash.get("info")) settings.html.options(_, flash.get("info"))
}) })
/** /**
* Save the repository options. * Save the repository options.
*/ */
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
saveRepositoryOptions( saveRepositoryOptions(
repository.owner, repository.owner,
repository.name, repository.name,
form.description, form.description,
if(repository.branchList.isEmpty) "master" else form.defaultBranch, if(repository.branchList.isEmpty) "master" else form.defaultBranch,
repository.repository.parentUserName.map { _ => repository.repository.parentUserName.map { _ =>
repository.repository.isPrivate repository.repository.isPrivate
} getOrElse form.isPrivate } getOrElse form.isPrivate
) )
// Change repository name // Change repository name
if(repository.name != form.repositoryName){ if(repository.name != form.repositoryName){
// Update database // Update database
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Move git repository // Move git repository
defining(getRepositoryDir(repository.owner, repository.name)){ dir => defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName)) FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
} }
// Move wiki repository // Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir => defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName)) FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
} }
} }
flash += "info" -> "Repository settings has been updated." flash += "info" -> "Repository settings has been updated."
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options") redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
}) })
/** /**
* Display the Collaborators page. * Display the Collaborators page.
*/ */
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
settings.html.collaborators( settings.html.collaborators(
getCollaborators(repository.owner, repository.name), getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.isGroupAccount, getAccountByUserName(repository.owner).get.isGroupAccount,
repository) repository)
}) })
/** /**
* Add the collaborator. * Add the collaborator.
*/ */
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
addCollaborator(repository.owner, repository.name, form.userName) addCollaborator(repository.owner, repository.name, form.userName)
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
}) })
/** /**
* Add the collaborator. * Add the collaborator.
*/ */
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
removeCollaborator(repository.owner, repository.name, params("name")) removeCollaborator(repository.owner, repository.name, params("name"))
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
}) })
/** /**
* Display the web hook page. * Display the web hook page.
*/ */
get("/:owner/:repository/settings/hooks")(ownerOnly { repository => get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info")) settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
}) })
/** /**
* Add the web hook URL. * Add the web hook URL.
*/ */
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
addWebHookURL(repository.owner, repository.name, form.url) addWebHookURL(repository.owner, repository.name, form.url)
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
/** /**
* Delete the web hook URL. * Delete the web hook URL.
*/ */
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository => get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
deleteWebHookURL(repository.owner, repository.name, params("url")) deleteWebHookURL(repository.owner, repository.name, params("url"))
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
/** /**
* Send the test request to registered web hook URLs. * Send the test request to registered web hook URLs.
*/ */
get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository => get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
val commits = git.log val commits = git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch)) .add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(3) .setMaxCount(3)
.call.iterator.asScala.map(new CommitInfo(_)) .call.iterator.asScala.map(new CommitInfo(_))
getWebHookURLs(repository.owner, repository.name) match { getWebHookURLs(repository.owner, repository.name) match {
case webHookURLs if(webHookURLs.nonEmpty) => case webHookURLs if(webHookURLs.nonEmpty) =>
for(ownerAccount <- getAccountByUserName(repository.owner)){ for(ownerAccount <- getAccountByUserName(repository.owner)){
callWebHook(repository.owner, repository.name, webHookURLs, callWebHook(repository.owner, repository.name, webHookURLs,
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)) WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount))
} }
case _ => case _ =>
} }
flash += "info" -> "Test payload deployed!" flash += "info" -> "Test payload deployed!"
} }
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
/** /**
* Display the danger zone. * Display the danger zone.
*/ */
get("/:owner/:repository/settings/danger")(ownerOnly { get("/:owner/:repository/settings/danger")(ownerOnly {
settings.html.danger(_) settings.html.danger(_)
}) })
/** /**
* Transfer repository ownership. * Transfer repository ownership.
*/ */
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
// Change repository owner // Change repository owner
if(repository.owner != form.newOwner){ if(repository.owner != form.newOwner){
LockUtil.lock(s"${repository.owner}/${repository.name}"){ LockUtil.lock(s"${repository.owner}/${repository.name}"){
// Update database // Update database
renameRepository(repository.owner, repository.name, form.newOwner, repository.name) renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Move git repository // Move git repository
defining(getRepositoryDir(repository.owner, repository.name)){ dir => defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name)) FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
} }
// Move wiki repository // Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir => defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name)) FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
} }
} }
} }
redirect(s"/${form.newOwner}/${repository.name}") redirect(s"/${form.newOwner}/${repository.name}")
}) })
/** /**
* Delete the repository. * Delete the repository.
*/ */
post("/:owner/:repository/settings/delete")(ownerOnly { repository => post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
LockUtil.lock(s"${repository.owner}/${repository.name}"){ LockUtil.lock(s"${repository.owner}/${repository.name}"){
deleteRepository(repository.owner, repository.name) deleteRepository(repository.owner, repository.name)
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name)) FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name)) FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name)) FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
} }
redirect(s"/${repository.owner}") redirect(s"/${repository.owner}")
}) })
/** /**
* Provides duplication check for web hook url. * Provides duplication check for web hook url.
*/ */
private def webHook: Constraint = new Constraint(){ private def webHook: 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] =
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.") getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
} }
/** /**
* Provides Constraint to validate the collaborator name. * Provides Constraint to validate the collaborator name.
*/ */
private def collaborator: Constraint = new Constraint(){ private def collaborator: 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] =
getAccountByUserName(value) match { getAccountByUserName(value) match {
case None => Some("User does not exist.") case None => Some("User does not exist.")
case Some(x) if(x.isGroupAccount) case Some(x) if(x.isGroupAccount)
=> Some("User does not exist.") => Some("User does not exist.")
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName)) case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
=> Some("User can access this repository already.") => Some("User can access this repository already.")
case _ => None case _ => None
} }
} }
/** /**
* Duplicate check for the rename repository name. * Duplicate check for the rename repository name.
*/ */
private def renameRepositoryName: Constraint = new Constraint(){ private def renameRepositoryName: Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
params.get("repository").filter(_ != value).flatMap { _ => params.get("repository").filter(_ != value).flatMap { _ =>
params.get("owner").flatMap { userName => params.get("owner").flatMap { userName =>
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.") getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
} }
} }
} }
/** /**
* Provides Constraint to validate the repository transfer user. * Provides Constraint to validate the repository transfer user.
*/ */
private def transferUser: Constraint = new Constraint(){ private def transferUser: 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] =
getAccountByUserName(value) match { getAccountByUserName(value) match {
case None => Some("User does not exist.") case None => Some("User does not exist.")
case Some(x) => if(x.userName == params("owner")){ case Some(x) => if(x.userName == params("owner")){
Some("This is current repository owner.") Some("This is current repository owner.")
} else { } else {
params.get("repository").flatMap { repositoryName => params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." } getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
} }
} }
} }
} }
} }

View File

@@ -1,34 +1,34 @@
package model package model
import scala.slick.driver.H2Driver.simple._ import scala.slick.driver.H2Driver.simple._
object Accounts extends Table[Account]("ACCOUNT") { object Accounts extends Table[Account]("ACCOUNT") {
def userName = column[String]("USER_NAME", O PrimaryKey) def userName = column[String]("USER_NAME", O PrimaryKey)
def fullName = column[String]("FULL_NAME") def fullName = column[String]("FULL_NAME")
def mailAddress = column[String]("MAIL_ADDRESS") def mailAddress = column[String]("MAIL_ADDRESS")
def password = column[String]("PASSWORD") def password = column[String]("PASSWORD")
def isAdmin = column[Boolean]("ADMINISTRATOR") def isAdmin = column[Boolean]("ADMINISTRATOR")
def url = column[String]("URL") def url = column[String]("URL")
def registeredDate = column[java.util.Date]("REGISTERED_DATE") def registeredDate = column[java.util.Date]("REGISTERED_DATE")
def updatedDate = column[java.util.Date]("UPDATED_DATE") def updatedDate = column[java.util.Date]("UPDATED_DATE")
def lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE") def lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
def image = column[String]("IMAGE") def image = column[String]("IMAGE")
def groupAccount = column[Boolean]("GROUP_ACCOUNT") def groupAccount = column[Boolean]("GROUP_ACCOUNT")
def removed = column[Boolean]("REMOVED") def removed = column[Boolean]("REMOVED")
def * = userName ~ fullName ~ mailAddress ~ password ~ isAdmin ~ url.? ~ registeredDate ~ updatedDate ~ lastLoginDate.? ~ image.? ~ groupAccount ~ removed <> (Account, Account.unapply _) def * = userName ~ fullName ~ mailAddress ~ password ~ isAdmin ~ url.? ~ registeredDate ~ updatedDate ~ lastLoginDate.? ~ image.? ~ groupAccount ~ removed <> (Account, Account.unapply _)
} }
case class Account( case class Account(
userName: String, userName: String,
fullName: String, fullName: String,
mailAddress: String, mailAddress: String,
password: String, password: String,
isAdmin: Boolean, isAdmin: Boolean,
url: Option[String], url: Option[String],
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date, updatedDate: java.util.Date,
lastLoginDate: Option[java.util.Date], lastLoginDate: Option[java.util.Date],
image: Option[String], image: Option[String],
isGroupAccount: Boolean, isGroupAccount: Boolean,
isRemoved: Boolean isRemoved: Boolean
) )

View File

@@ -1,44 +1,44 @@
package model package model
import scala.slick.driver.H2Driver.simple._ import scala.slick.driver.H2Driver.simple._
protected[model] trait BasicTemplate { self: Table[_] => protected[model] trait BasicTemplate { self: Table[_] =>
def userName = column[String]("USER_NAME") def userName = column[String]("USER_NAME")
def repositoryName = column[String]("REPOSITORY_NAME") def 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 is owner.bind) && (repositoryName is 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 is userName) && (this.repositoryName is repositoryName)
} }
protected[model] trait IssueTemplate extends BasicTemplate { self: Table[_] => protected[model] trait IssueTemplate extends BasicTemplate { self: Table[_] =>
def issueId = column[Int]("ISSUE_ID") def 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 is 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 is issueId)
} }
protected[model] trait LabelTemplate extends BasicTemplate { self: Table[_] => protected[model] trait LabelTemplate extends BasicTemplate { self: Table[_] =>
def labelId = column[Int]("LABEL_ID") def 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 is 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 is labelId)
} }
protected[model] trait MilestoneTemplate extends BasicTemplate { self: Table[_] => protected[model] trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
def milestoneId = column[Int]("MILESTONE_ID") def 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 is 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 is milestoneId)
} }

View File

@@ -1,41 +1,41 @@
package model package model
import scala.slick.driver.H2Driver.simple._ import scala.slick.driver.H2Driver.simple._
object IssueId extends Table[(String, String, Int)]("ISSUE_ID") with IssueTemplate { object IssueId extends Table[(String, String, Int)]("ISSUE_ID") with IssueTemplate {
def * = userName ~ repositoryName ~ issueId def * = userName ~ repositoryName ~ issueId
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
} }
object IssueOutline extends Table[(String, String, Int, Int)]("ISSUE_OUTLINE_VIEW") with IssueTemplate { object IssueOutline extends Table[(String, String, Int, Int)]("ISSUE_OUTLINE_VIEW") with IssueTemplate {
def commentCount = column[Int]("COMMENT_COUNT") def commentCount = column[Int]("COMMENT_COUNT")
def * = userName ~ repositoryName ~ issueId ~ commentCount def * = userName ~ repositoryName ~ issueId ~ commentCount
} }
object Issues extends Table[Issue]("ISSUE") with IssueTemplate with MilestoneTemplate { object Issues extends Table[Issue]("ISSUE") with IssueTemplate with MilestoneTemplate {
def openedUserName = column[String]("OPENED_USER_NAME") def openedUserName = column[String]("OPENED_USER_NAME")
def assignedUserName = column[String]("ASSIGNED_USER_NAME") def assignedUserName = column[String]("ASSIGNED_USER_NAME")
def title = column[String]("TITLE") def title = column[String]("TITLE")
def content = column[String]("CONTENT") def content = column[String]("CONTENT")
def closed = column[Boolean]("CLOSED") def closed = column[Boolean]("CLOSED")
def registeredDate = column[java.util.Date]("REGISTERED_DATE") def registeredDate = column[java.util.Date]("REGISTERED_DATE")
def updatedDate = column[java.util.Date]("UPDATED_DATE") def updatedDate = column[java.util.Date]("UPDATED_DATE")
def pullRequest = column[Boolean]("PULL_REQUEST") def pullRequest = column[Boolean]("PULL_REQUEST")
def * = userName ~ repositoryName ~ issueId ~ openedUserName ~ milestoneId.? ~ assignedUserName.? ~ title ~ content.? ~ closed ~ registeredDate ~ updatedDate ~ pullRequest <> (Issue, Issue.unapply _) def * = userName ~ repositoryName ~ issueId ~ openedUserName ~ milestoneId.? ~ assignedUserName.? ~ title ~ content.? ~ closed ~ registeredDate ~ updatedDate ~ pullRequest <> (Issue, Issue.unapply _)
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,
openedUserName: String, openedUserName: String,
milestoneId: Option[Int], milestoneId: Option[Int],
assignedUserName: Option[String], assignedUserName: Option[String],
title: String, title: String,
content: Option[String], content: Option[String],
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

@@ -1,28 +1,28 @@
package model package model
import scala.slick.driver.H2Driver.simple._ import scala.slick.driver.H2Driver.simple._
object IssueComments extends Table[IssueComment]("ISSUE_COMMENT") with IssueTemplate { object IssueComments extends Table[IssueComment]("ISSUE_COMMENT") with IssueTemplate {
def commentId = column[Int]("COMMENT_ID", O AutoInc) def commentId = column[Int]("COMMENT_ID", O AutoInc)
def action = column[String]("ACTION") def action = column[String]("ACTION")
def commentedUserName = column[String]("COMMENTED_USER_NAME") def commentedUserName = column[String]("COMMENTED_USER_NAME")
def content = column[String]("CONTENT") def content = column[String]("CONTENT")
def registeredDate = column[java.util.Date]("REGISTERED_DATE") def registeredDate = column[java.util.Date]("REGISTERED_DATE")
def updatedDate = column[java.util.Date]("UPDATED_DATE") def updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = userName ~ repositoryName ~ issueId ~ commentId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate <> (IssueComment, IssueComment.unapply _) def * = userName ~ repositoryName ~ issueId ~ commentId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate <> (IssueComment, IssueComment.unapply _)
def autoInc = userName ~ repositoryName ~ issueId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate returning commentId def autoInc = userName ~ repositoryName ~ issueId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate returning commentId
def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
} }
case class IssueComment( case class IssueComment(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
issueId: Int, issueId: Int,
commentId: Int, commentId: Int,
action: String, action: String,
commentedUserName: String, commentedUserName: String,
content: String, content: String,
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date updatedDate: java.util.Date
) )

View File

@@ -1,20 +1,20 @@
package object model { package object model {
import scala.slick.driver.BasicDriver.Implicit._ import scala.slick.driver.BasicDriver.Implicit._
import scala.slick.lifted.{Column, MappedTypeMapper} import scala.slick.lifted.{Column, MappedTypeMapper}
// java.util.Date TypeMapper // java.util.Date TypeMapper
implicit val dateTypeMapper = MappedTypeMapper.base[java.util.Date, java.sql.Timestamp]( implicit val dateTypeMapper = MappedTypeMapper.base[java.util.Date, java.sql.Timestamp](
d => new java.sql.Timestamp(d.getTime), d => new java.sql.Timestamp(d.getTime),
t => new java.util.Date(t.getTime) t => new java.util.Date(t.getTime)
) )
implicit class RichColumn(c1: Column[Boolean]){ implicit class RichColumn(c1: Column[Boolean]){
def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1 def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
} }
/** /**
* Returns system date. * Returns system date.
*/ */
def currentDate = new java.util.Date() def currentDate = new java.util.Date()
} }

View File

@@ -1,380 +1,380 @@
package service package service
import scala.slick.driver.H2Driver.simple._ import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession import Database.threadLocalSession
import scala.slick.jdbc.{StaticQuery => Q} import scala.slick.jdbc.{StaticQuery => Q}
import Q.interpolation import Q.interpolation
import model._ import model._
import util.Implicits._ import util.Implicits._
import util.StringUtil._ import util.StringUtil._
trait IssuesService { trait IssuesService {
import IssuesService._ import IssuesService._
def getIssue(owner: String, repository: String, issueId: String) = def getIssue(owner: String, repository: String, issueId: String) =
if (issueId forall (_.isDigit)) if (issueId forall (_.isDigit))
Query(Issues) filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption Query(Issues) filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
else None else None
def getComments(owner: String, repository: String, issueId: Int) = def getComments(owner: String, repository: String, issueId: Int) =
Query(IssueComments) filter (_.byIssue(owner, repository, issueId)) list Query(IssueComments) filter (_.byIssue(owner, repository, issueId)) list
def getComment(owner: String, repository: String, commentId: String) = def getComment(owner: String, repository: String, commentId: String) =
if (commentId forall (_.isDigit)) if (commentId forall (_.isDigit))
Query(IssueComments) filter { t => Query(IssueComments) filter { t =>
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository) t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
} firstOption } firstOption
else None else None
def getIssueLabels(owner: String, repository: String, issueId: Int) = def getIssueLabels(owner: String, repository: String, issueId: Int) =
IssueLabels IssueLabels
.innerJoin(Labels).on { (t1, t2) => .innerJoin(Labels).on { (t1, t2) =>
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId) t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
} }
.filter ( _._1.byIssue(owner, repository, issueId) ) .filter ( _._1.byIssue(owner, repository, issueId) )
.map ( _._2 ) .map ( _._2 )
.list .list
def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) = def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
Query(IssueLabels) filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption Query(IssueLabels) filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
/** /**
* Returns the count of the search result against issues. * Returns the count of the search result against issues.
* *
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
* @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request. * @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
* @param repos Tuple of the repository owner and the repository name * @param repos Tuple of the repository owner and the repository name
* @return the count of the search result * @return the count of the search result
*/ */
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
repos: (String, String)*): Int = repos: (String, String)*): Int =
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
/** /**
* Returns the Map which contains issue count for each labels. * Returns the Map which contains issue count for each labels.
* *
* @param owner the repository owner * @param owner the repository owner
* @param repository the repository name * @param repository the repository name
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
* @return the Map which contains issue count for each labels (key is label name, value is issue count) * @return the Map which contains issue count for each labels (key is label name, value is issue count)
*/ */
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition, def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
filterUser: Map[String, String]): Map[String, Int] = { filterUser: Map[String, String]): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false) searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
.innerJoin(IssueLabels).on { (t1, t2) => .innerJoin(IssueLabels).on { (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.innerJoin(Labels).on { case ((t1, t2), t3) => .innerJoin(Labels).on { case ((t1, t2), t3) =>
t2.byLabel(t3.userName, t3.repositoryName, t3.labelId) t2.byLabel(t3.userName, t3.repositoryName, t3.labelId)
} }
.groupBy { case ((t1, t2), t3) => .groupBy { case ((t1, t2), t3) =>
t3.labelName t3.labelName
} }
.map { case (labelName, t) => .map { case (labelName, t) =>
labelName ~ t.length labelName ~ t.length
} }
.toMap .toMap
} }
/** /**
* Returns list which contains issue count for each repository. * Returns list which contains issue count for each repository.
* If the issue does not exist, its repository is not included in the result. * If the issue does not exist, its repository is not included in the result.
* *
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request. * @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
* @param repos Tuple of the repository owner and the repository name * @param repos Tuple of the repository owner and the repository name
* @return list which contains issue count for each repository * @return list which contains issue count for each repository
*/ */
def countIssueGroupByRepository( def countIssueGroupByRepository(
condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
repos: (String, String)*): List[(String, String, Int)] = { repos: (String, String)*): List[(String, String, Int)] = {
searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest) searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
.groupBy { t => .groupBy { t =>
t.userName ~ t.repositoryName t.userName ~ t.repositoryName
} }
.map { case (repo, t) => .map { case (repo, t) =>
repo ~ t.length repo ~ t.length
} }
.sortBy(_._3 desc) .sortBy(_._3 desc)
.list .list
} }
/** /**
* Returns the search result against issues. * Returns the search result against issues.
* *
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_created_by", value is the user name) * @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_created_by", value is the user name)
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request. * @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
* @param offset the offset for pagination * @param offset the offset for pagination
* @param limit the limit for pagination * @param limit the limit for pagination
* @param repos Tuple of the repository owner and the repository name * @param repos Tuple of the repository owner and the repository name
* @return the search result (list of tuples which contain issue, labels and comment count) * @return the search result (list of tuples which contain issue, labels and comment count)
*/ */
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = { offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = {
// get issues and comment count and labels // get issues and comment count and labels
searchIssueQuery(repos, condition, filterUser, onlyPullRequest) searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
.sortBy { case (t1, t2) => .sortBy { case (t1, t2) =>
(condition.sort match { (condition.sort match {
case "created" => t1.registeredDate case "created" => t1.registeredDate
case "comments" => t2.commentCount case "comments" => t2.commentCount
case "updated" => t1.updatedDate case "updated" => t1.updatedDate
}) match { }) match {
case sort => condition.direction match { case sort => condition.direction match {
case "asc" => sort asc case "asc" => sort asc
case "desc" => sort desc case "desc" => sort desc
} }
} }
} }
.drop(offset).take(limit) .drop(offset).take(limit)
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } .leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
.leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) } .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
.map { case (((t1, t2), t3), t4) => .map { case (((t1, t2), t3), t4) =>
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?) (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
} }
.list .list
.splitWith { (c1, c2) => .splitWith { (c1, c2) =>
c1._1.userName == c2._1.userName && c1._1.userName == c2._1.userName &&
c1._1.repositoryName == c2._1.repositoryName && c1._1.repositoryName == c2._1.repositoryName &&
c1._1.issueId == c2._1.issueId c1._1.issueId == c2._1.issueId
} }
.map { issues => issues.head match { .map { issues => issues.head match {
case (issue, commentCount, _,_,_) => case (issue, commentCount, _,_,_) =>
(issue, (issue,
issues.flatMap { t => t._3.map ( issues.flatMap { t => t._3.map (
Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get) Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
)} toList, )} toList,
commentCount) commentCount)
}} toList }} toList
} }
/** /**
* Assembles query for conditional issue searching. * Assembles query for conditional issue searching.
*/ */
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
filterUser: Map[String, String], onlyPullRequest: Boolean) = filterUser: Map[String, String], onlyPullRequest: Boolean) =
Query(Issues) filter { t1 => Query(Issues) filter { t1 =>
condition.repo condition.repo
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } } .map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
.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 is (condition.state == "closed").bind) &&
(t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) && (t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
(t1.milestoneId isNull, condition.milestoneId == Some(None)) && (t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) && (t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) && (t1.openedUserName is 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 isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
(t1.pullRequest is true.bind, onlyPullRequest) && (t1.pullRequest is 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
(Labels filter { t3 => (Labels filter { t3 =>
(t3.byRepository(t1.userName, t1.repositoryName)) && (t3.byRepository(t1.userName, t1.repositoryName)) &&
(t3.labelName inSetBind condition.labels) (t3.labelName inSetBind condition.labels)
} map(_.labelId))) } map(_.labelId)))
} exists, condition.labels.nonEmpty) } exists, condition.labels.nonEmpty)
} }
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String], def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], isPullRequest: Boolean = false) = assignedUserName: Option[String], milestoneId: Option[Int], isPullRequest: Boolean = false) =
// next id number // next id number
sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int] sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
.firstOption.filter { id => .firstOption.filter { id =>
Issues insert Issue( Issues insert Issue(
owner, owner,
repository, repository,
id, id,
loginUser, loginUser,
milestoneId, milestoneId,
assignedUserName, assignedUserName,
title, title,
content, content,
false, false,
currentDate, currentDate,
currentDate, currentDate,
isPullRequest) isPullRequest)
// increment issue id // increment issue id
IssueId IssueId
.filter (_.byPrimaryKey(owner, repository)) .filter (_.byPrimaryKey(owner, repository))
.map (_.issueId) .map (_.issueId)
.update (id) > 0 .update (id) > 0
} get } get
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) = def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
IssueLabels insert (IssueLabel(owner, repository, issueId, labelId)) IssueLabels insert (IssueLabel(owner, repository, issueId, labelId))
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) = def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
def createComment(owner: String, repository: String, loginUser: String, def createComment(owner: String, repository: String, loginUser: String,
issueId: Int, content: String, action: String) = issueId: Int, content: String, action: String) =
IssueComments.autoInc insert ( IssueComments.autoInc insert (
owner, owner,
repository, repository,
issueId, issueId,
action, action,
loginUser, loginUser,
content, content,
currentDate, currentDate,
currentDate) currentDate)
def updateIssue(owner: String, repository: String, issueId: Int, def updateIssue(owner: String, repository: String, issueId: Int,
title: String, content: Option[String]) = title: String, content: Option[String]) =
Issues Issues
.filter (_.byPrimaryKey(owner, repository, issueId)) .filter (_.byPrimaryKey(owner, repository, issueId))
.map { t => .map { t =>
t.title ~ t.content.? ~ t.updatedDate t.title ~ t.content.? ~ t.updatedDate
} }
.update (title, content, currentDate) .update (title, content, currentDate)
def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String]) = def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String]) =
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName) Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int]) = def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int]) =
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId) Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
def updateComment(commentId: Int, content: String) = def updateComment(commentId: Int, content: String) =
IssueComments IssueComments
.filter (_.byPrimaryKey(commentId)) .filter (_.byPrimaryKey(commentId))
.map { t => .map { t =>
t.content ~ t.updatedDate t.content ~ t.updatedDate
} }
.update (content, currentDate) .update (content, currentDate)
def deleteComment(commentId: Int) = def deleteComment(commentId: Int) =
IssueComments filter (_.byPrimaryKey(commentId)) delete IssueComments filter (_.byPrimaryKey(commentId)) delete
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean) = def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean) =
Issues Issues
.filter (_.byPrimaryKey(owner, repository, issueId)) .filter (_.byPrimaryKey(owner, repository, issueId))
.map { t => .map { t =>
t.closed ~ t.updatedDate t.closed ~ t.updatedDate
} }
.update (closed, currentDate) .update (closed, currentDate)
/** /**
* Search issues by keyword. * Search issues by keyword.
* *
* @param owner the repository owner * @param owner the repository owner
* @param repository the repository name * @param repository the repository name
* @param query the keywords separated by whitespace. * @param query the keywords separated by whitespace.
* @return issues with comment count and matched content of issue or comment * @return issues with comment count and matched content of issue or comment
*/ */
def searchIssuesByKeyword(owner: String, repository: String, query: String): List[(Issue, Int, String)] = { def searchIssuesByKeyword(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
import scala.slick.driver.H2Driver.likeEncode import scala.slick.driver.H2Driver.likeEncode
val keywords = splitWords(query.toLowerCase) val keywords = splitWords(query.toLowerCase)
// Search Issue // Search Issue
val issues = Issues val issues = Issues
.innerJoin(IssueOutline).on { case (t1, t2) => .innerJoin(IssueOutline).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { case (t1, t2) => .filter { case (t1, t2) =>
keywords.map { keyword => keywords.map { keyword =>
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) || (t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) (t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
} .reduceLeft(_ && _) } .reduceLeft(_ && _)
} }
.map { case (t1, t2) => .map { case (t1, t2) =>
(t1, 0, t1.content.?, t2.commentCount) (t1, 0, t1.content.?, t2.commentCount)
} }
// Search IssueComment // Search IssueComment
val comments = IssueComments val comments = IssueComments
.innerJoin(Issues).on { case (t1, t2) => .innerJoin(Issues).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.innerJoin(IssueOutline).on { case ((t1, t2), t3) => .innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
t2.byIssue(t3.userName, t3.repositoryName, t3.issueId) t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
} }
.filter { case ((t1, t2), t3) => .filter { case ((t1, t2), t3) =>
keywords.map { query => keywords.map { query =>
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^') t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
}.reduceLeft(_ && _) }.reduceLeft(_ && _)
} }
.map { case ((t1, t2), t3) => .map { case ((t1, t2), t3) =>
(t2, t1.commentId, t1.content.?, t3.commentCount) (t2, t1.commentId, t1.content.?, t3.commentCount)
} }
issues.union(comments).sortBy { case (issue, commentId, _, _) => issues.union(comments).sortBy { case (issue, commentId, _, _) =>
issue.issueId ~ commentId issue.issueId ~ commentId
}.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) => }.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
issue1.issueId == issue2.issueId issue1.issueId == issue2.issueId
}.map { _.head match { }.map { _.head match {
case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse("")) case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
} }
}.toList }.toList
} }
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String) = { def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String) = {
extractCloseId(message).foreach { issueId => extractCloseId(message).foreach { issueId =>
for(issue <- getIssue(owner, repository, issueId) if !issue.closed){ for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
createComment(owner, repository, userName, issue.issueId, "Close", "close") createComment(owner, repository, userName, issue.issueId, "Close", "close")
updateClosed(owner, repository, issue.issueId, true) updateClosed(owner, repository, issue.issueId, true)
} }
} }
} }
} }
object IssuesService { object IssuesService {
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
val IssueLimit = 30 val IssueLimit = 30
case class IssueSearchCondition( case class IssueSearchCondition(
labels: Set[String] = Set.empty, labels: Set[String] = Set.empty,
milestoneId: Option[Option[Int]] = None, milestoneId: Option[Option[Int]] = None,
repo: Option[String] = None, repo: Option[String] = None,
state: String = "open", state: String = "open",
sort: String = "created", sort: String = "created",
direction: String = "desc"){ direction: String = "desc"){
def toURL: String = def toURL: String =
"?" + List( "?" + List(
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))), if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
milestoneId.map { id => "milestone=" + (id match { milestoneId.map { id => "milestone=" + (id match {
case Some(x) => x.toString case Some(x) => x.toString
case None => "none" case None => "none"
})}, })},
repo.map("for=" + urlEncode(_)), repo.map("for=" + urlEncode(_)),
Some("state=" + urlEncode(state)), Some("state=" + urlEncode(state)),
Some("sort=" + urlEncode(sort)), Some("sort=" + urlEncode(sort)),
Some("direction=" + urlEncode(direction))).flatten.mkString("&") Some("direction=" + urlEncode(direction))).flatten.mkString("&")
} }
object IssueSearchCondition { object IssueSearchCondition {
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = { private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
val value = request.getParameter(name) val value = request.getParameter(name)
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value) if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
} }
def apply(request: HttpServletRequest): IssueSearchCondition = def apply(request: HttpServletRequest): IssueSearchCondition =
IssueSearchCondition( IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty), param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
param(request, "milestone").map{ param(request, "milestone").map{
case "none" => None case "none" => None
case x => x.toIntOpt case x => x.toIntOpt
}, },
param(request, "for"), param(request, "for"),
param(request, "state", Seq("open", "closed")).getOrElse("open"), param(request, "state", Seq("open", "closed")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"), param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc")) param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
def page(request: HttpServletRequest) = try { def page(request: HttpServletRequest) = try {
val i = param(request, "page").getOrElse("1").toInt val i = param(request, "page").getOrElse("1").toInt
if(i <= 0) 1 else i if(i <= 0) 1 else i
} catch { } catch {
case e: NumberFormatException => 1 case e: NumberFormatException => 1
} }
} }
} }

View File

@@ -1,190 +1,190 @@
package service package service
import util.Directory._ import util.Directory._
import util.ControlUtil._ import util.ControlUtil._
import SystemSettingsService._ import SystemSettingsService._
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
trait SystemSettingsService { trait SystemSettingsService {
def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request) def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
def saveSystemSettings(settings: SystemSettings): Unit = { def saveSystemSettings(settings: SystemSettings): Unit = {
defining(new java.util.Properties()){ props => defining(new java.util.Properties()){ props =>
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", ""))) settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString) props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
props.setProperty(Gravatar, settings.gravatar.toString) props.setProperty(Gravatar, settings.gravatar.toString)
props.setProperty(Notification, settings.notification.toString) props.setProperty(Notification, settings.notification.toString)
props.setProperty(Ssh, settings.ssh.toString) props.setProperty(Ssh, settings.ssh.toString)
settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
if(settings.notification) { if(settings.notification) {
settings.smtp.foreach { smtp => settings.smtp.foreach { smtp =>
props.setProperty(SmtpHost, smtp.host) props.setProperty(SmtpHost, smtp.host)
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString)) smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
smtp.user.foreach(props.setProperty(SmtpUser, _)) smtp.user.foreach(props.setProperty(SmtpUser, _))
smtp.password.foreach(props.setProperty(SmtpPassword, _)) smtp.password.foreach(props.setProperty(SmtpPassword, _))
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString)) smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _)) smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
smtp.fromName.foreach(props.setProperty(SmtpFromName, _)) smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
} }
} }
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString) props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
if(settings.ldapAuthentication){ if(settings.ldapAuthentication){
settings.ldap.map { ldap => settings.ldap.map { ldap =>
props.setProperty(LdapHost, ldap.host) props.setProperty(LdapHost, ldap.host)
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString)) ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x)) ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
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.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x)) ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute) props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
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))
} }
} }
using(new java.io.FileOutputStream(GitBucketConf)){ out => using(new java.io.FileOutputStream(GitBucketConf)){ out =>
props.store(out, null) props.store(out, null)
} }
} }
} }
def loadSystemSettings(): SystemSettings = { def loadSystemSettings(): SystemSettings = {
defining(new java.util.Properties()){ props => defining(new java.util.Properties()){ props =>
if(GitBucketConf.exists){ if(GitBucketConf.exists){
using(new java.io.FileInputStream(GitBucketConf)){ in => using(new java.io.FileInputStream(GitBucketConf)){ in =>
props.load(in) props.load(in)
} }
} }
SystemSettings( SystemSettings(
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")), getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
getValue(props, AllowAccountRegistration, false), getValue(props, AllowAccountRegistration, false),
getValue(props, Gravatar, true), getValue(props, Gravatar, true),
getValue(props, Notification, false), getValue(props, Notification, false),
getValue(props, Ssh, false), getValue(props, Ssh, false),
getOptionValue(props, SshPort, Some(DefaultSshPort)), getOptionValue(props, SshPort, Some(DefaultSshPort)),
if(getValue(props, Notification, false)){ if(getValue(props, Notification, false)){
Some(Smtp( Some(Smtp(
getValue(props, SmtpHost, ""), getValue(props, SmtpHost, ""),
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)), getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
getOptionValue(props, SmtpUser, None), getOptionValue(props, SmtpUser, None),
getOptionValue(props, SmtpPassword, None), getOptionValue(props, SmtpPassword, None),
getOptionValue[Boolean](props, SmtpSsl, None), getOptionValue[Boolean](props, SmtpSsl, None),
getOptionValue(props, SmtpFromAddress, None), getOptionValue(props, SmtpFromAddress, None),
getOptionValue(props, SmtpFromName, None))) getOptionValue(props, SmtpFromName, None)))
} else { } else {
None None
}, },
getValue(props, LdapAuthentication, false), getValue(props, LdapAuthentication, false),
if(getValue(props, LdapAuthentication, false)){ if(getValue(props, LdapAuthentication, false)){
Some(Ldap( Some(Ldap(
getValue(props, LdapHost, ""), getValue(props, LdapHost, ""),
getOptionValue(props, LdapPort, Some(DefaultLdapPort)), getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
getOptionValue(props, LdapBindDN, None), getOptionValue(props, LdapBindDN, None),
getOptionValue(props, LdapBindPassword, None), getOptionValue(props, LdapBindPassword, None),
getValue(props, LdapBaseDN, ""), getValue(props, LdapBaseDN, ""),
getValue(props, LdapUserNameAttribute, ""), getValue(props, LdapUserNameAttribute, ""),
getOptionValue(props, LdapFullNameAttribute, None), getOptionValue(props, LdapFullNameAttribute, None),
getValue(props, LdapMailAddressAttribute, ""), getValue(props, LdapMailAddressAttribute, ""),
getOptionValue[Boolean](props, LdapTls, None), getOptionValue[Boolean](props, LdapTls, None),
getOptionValue(props, LdapKeystore, None))) getOptionValue(props, LdapKeystore, None)))
} else { } else {
None None
} }
) )
} }
} }
} }
object SystemSettingsService { object SystemSettingsService {
import scala.reflect.ClassTag import scala.reflect.ClassTag
case class SystemSettings( case class SystemSettings(
baseUrl: Option[String], baseUrl: Option[String],
allowAccountRegistration: Boolean, allowAccountRegistration: Boolean,
gravatar: Boolean, gravatar: Boolean,
notification: Boolean, notification: Boolean,
ssh: Boolean, ssh: Boolean,
sshPort: Option[Int], sshPort: Option[Int],
smtp: Option[Smtp], smtp: Option[Smtp],
ldapAuthentication: Boolean, ldapAuthentication: Boolean,
ldap: Option[Ldap]){ ldap: Option[Ldap]){
def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse { def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse {
defining(request.getRequestURL.toString){ url => defining(request.getRequestURL.toString){ url =>
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length)) url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
} }
}.stripSuffix("/") }.stripSuffix("/")
} }
case class Ldap( case class Ldap(
host: String, host: String,
port: Option[Int], port: Option[Int],
bindDN: Option[String], bindDN: Option[String],
bindPassword: Option[String], bindPassword: Option[String],
baseDN: String, baseDN: String,
userNameAttribute: String, userNameAttribute: String,
fullNameAttribute: Option[String], fullNameAttribute: Option[String],
mailAttribute: String, mailAttribute: String,
tls: Option[Boolean], tls: Option[Boolean],
keystore: Option[String]) keystore: Option[String])
case class Smtp( case class Smtp(
host: String, host: String,
port: Option[Int], port: Option[Int],
user: Option[String], user: Option[String],
password: Option[String], password: Option[String],
ssl: Option[Boolean], ssl: Option[Boolean],
fromAddress: Option[String], fromAddress: Option[String],
fromName: Option[String]) fromName: Option[String])
val DefaultSshPort = 29418 val DefaultSshPort = 29418
val DefaultSmtpPort = 25 val DefaultSmtpPort = 25
val DefaultLdapPort = 389 val DefaultLdapPort = 389
private val BaseURL = "base_url" private val BaseURL = "base_url"
private val AllowAccountRegistration = "allow_account_registration" private val AllowAccountRegistration = "allow_account_registration"
private val Gravatar = "gravatar" private val Gravatar = "gravatar"
private val Notification = "notification" private val Notification = "notification"
private val Ssh = "ssh" private val Ssh = "ssh"
private val SshPort = "ssh.port" private val SshPort = "ssh.port"
private val SmtpHost = "smtp.host" private val SmtpHost = "smtp.host"
private val SmtpPort = "smtp.port" private val SmtpPort = "smtp.port"
private val SmtpUser = "smtp.user" private val SmtpUser = "smtp.user"
private val SmtpPassword = "smtp.password" private val SmtpPassword = "smtp.password"
private val SmtpSsl = "smtp.ssl" private val SmtpSsl = "smtp.ssl"
private val SmtpFromAddress = "smtp.from_address" private val SmtpFromAddress = "smtp.from_address"
private val SmtpFromName = "smtp.from_name" private val SmtpFromName = "smtp.from_name"
private val LdapAuthentication = "ldap_authentication" private val LdapAuthentication = "ldap_authentication"
private val LdapHost = "ldap.host" private val LdapHost = "ldap.host"
private val LdapPort = "ldap.port" private val LdapPort = "ldap.port"
private val LdapBindDN = "ldap.bindDN" private val LdapBindDN = "ldap.bindDN"
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 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"
private val LdapKeystore = "ldap.keystore" private val LdapKeystore = "ldap.keystore"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
defining(props.getProperty(key)){ value => defining(props.getProperty(key)){ value =>
if(value == null || value.isEmpty) default if(value == null || value.isEmpty) default
else convertType(value).asInstanceOf[A] else convertType(value).asInstanceOf[A]
} }
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] =
defining(props.getProperty(key)){ value => defining(props.getProperty(key)){ value =>
if(value == null || value.isEmpty) default if(value == null || value.isEmpty) default
else Some(convertType(value)).asInstanceOf[Option[A]] else Some(convertType(value)).asInstanceOf[Option[A]]
} }
private def convertType[A: ClassTag](value: String) = private def convertType[A: ClassTag](value: String) =
defining(implicitly[ClassTag[A]].runtimeClass){ c => defining(implicitly[ClassTag[A]].runtimeClass){ c =>
if(c == classOf[Boolean]) value.toBoolean if(c == classOf[Boolean]) value.toBoolean
else if(c == classOf[Int]) value.toInt else if(c == classOf[Int]) value.toInt
else value else value
} }
} }

View File

@@ -1,282 +1,282 @@
package service package service
import java.util.Date import java.util.Date
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import util._ import util._
import _root_.util.ControlUtil._ import _root_.util.ControlUtil._
import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser} import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser}
import org.eclipse.jgit.lib._ import org.eclipse.jgit.lib._
import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry} import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry}
import org.eclipse.jgit.revwalk.RevWalk import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter} import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter}
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import org.eclipse.jgit.patch._ import org.eclipse.jgit.patch._
import org.eclipse.jgit.api.errors.PatchFormatException import org.eclipse.jgit.api.errors.PatchFormatException
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.Some import scala.Some
import service.RepositoryService.RepositoryInfo import service.RepositoryService.RepositoryInfo
object WikiService { object WikiService {
/** /**
* The model for wiki page. * The model for wiki page.
* *
* @param name the page name * @param name the page name
* @param content the page content * @param content the page content
* @param committer the last committer * @param committer the last committer
* @param time the last modified time * @param time the last modified time
* @param id the latest commit id * @param id the latest commit id
*/ */
case class WikiPageInfo(name: String, content: String, committer: String, time: Date, id: String) case class WikiPageInfo(name: String, content: String, committer: String, time: Date, id: String)
/** /**
* The model for wiki page history. * The model for wiki page history.
* *
* @param name the page name * @param name the page name
* @param committer the committer the committer * @param committer the committer the committer
* @param message the commit message * @param message the commit message
* @param date the commit date * @param date the commit date
*/ */
case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date) case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git") def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git")
def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings, userName: String) = def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings, userName: String) =
repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), userName).replaceFirst("\\.git\\Z", ".wiki.git") repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), userName).replaceFirst("\\.git\\Z", ".wiki.git")
} }
trait WikiService { trait WikiService {
import WikiService._ import WikiService._
def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit = def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit =
LockUtil.lock(s"${owner}/${repository}/wiki"){ LockUtil.lock(s"${owner}/${repository}/wiki"){
defining(Directory.getWikiRepositoryDir(owner, repository)){ dir => defining(Directory.getWikiRepositoryDir(owner, repository)){ dir =>
if(!dir.exists){ if(!dir.exists){
JGitUtil.initRepository(dir) JGitUtil.initRepository(dir)
saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None) saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None)
} }
} }
} }
/** /**
* Returns the wiki page. * Returns the wiki page.
*/ */
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = { def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
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.committer, file.time, file.commitId)
} }
} else None } else None
} }
} }
/** /**
* Returns the content of the specified file. * Returns the content of the specified file.
*/ */
def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] = def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] =
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
if(!JGitUtil.isEmpty(git)){ if(!JGitUtil.isEmpty(git)){
val index = path.lastIndexOf('/') val index = path.lastIndexOf('/')
val parentPath = if(index < 0) "." else path.substring(0, index) val parentPath = if(index < 0) "." else path.substring(0, index)
val fileName = if(index < 0) path else path.substring(index + 1) val fileName = if(index < 0) path else path.substring(index + 1)
JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file => JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
git.getRepository.open(file.id).getBytes git.getRepository.open(file.id).getBytes
} }
} else None } else None
} }
/** /**
* Returns the list of wiki page names. * Returns the list of wiki page names.
*/ */
def getWikiPageList(owner: String, repository: String): List[String] = { def getWikiPageList(owner: String, repository: String): List[String] = {
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
JGitUtil.getFileList(git, "master", ".") JGitUtil.getFileList(git, "master", ".")
.filter(_.name.endsWith(".md")) .filter(_.name.endsWith(".md"))
.map(_.name.stripSuffix(".md")) .map(_.name.stripSuffix(".md"))
.sortBy(x => x) .sortBy(x => x)
} }
} }
/** /**
* Reverts specified changes. * Reverts specified changes.
*/ */
def revertWikiPage(owner: String, repository: String, from: String, to: String, def revertWikiPage(owner: String, repository: String, from: String, to: String,
committer: model.Account, pageName: Option[String]): Boolean = { committer: model.Account, pageName: Option[String]): Boolean = {
case class RevertInfo(operation: String, filePath: String, source: String) case class RevertInfo(operation: String, filePath: String, source: String)
try { try {
LockUtil.lock(s"${owner}/${repository}/wiki"){ LockUtil.lock(s"${owner}/${repository}/wiki"){
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
val reader = git.getRepository.newObjectReader val reader = git.getRepository.newObjectReader
val oldTreeIter = new CanonicalTreeParser val oldTreeIter = new CanonicalTreeParser
oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}"))
val newTreeIter = new CanonicalTreeParser val newTreeIter = new CanonicalTreeParser
newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}"))
val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff =>
pageName match { pageName match {
case Some(x) => diff.getNewPath == x + ".md" case Some(x) => diff.getNewPath == x + ".md"
case None => true case None => true
} }
} }
val patch = using(new java.io.ByteArrayOutputStream()){ out => val patch = using(new java.io.ByteArrayOutputStream()){ out =>
val formatter = new DiffFormatter(out) val formatter = new DiffFormatter(out)
formatter.setRepository(git.getRepository) formatter.setRepository(git.getRepository)
formatter.format(diffs.asJava) formatter.format(diffs.asJava)
new String(out.toByteArray, "UTF-8") new String(out.toByteArray, "UTF-8")
} }
val p = new Patch() val p = new Patch()
p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8"))) p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8")))
if(!p.getErrors.isEmpty){ if(!p.getErrors.isEmpty){
throw new PatchFormatException(p.getErrors()) throw new PatchFormatException(p.getErrors())
} }
val revertInfo = (p.getFiles.asScala.map { fh => val revertInfo = (p.getFiles.asScala.map { fh =>
fh.getChangeType match { fh.getChangeType match {
case DiffEntry.ChangeType.MODIFY => { case DiffEntry.ChangeType.MODIFY => {
val source = getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("") val source = getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("")
val applied = PatchUtil.apply(source, patch, fh) val applied = PatchUtil.apply(source, patch, fh)
if(applied != null){ if(applied != null){
Seq(RevertInfo("ADD", fh.getNewPath, applied)) Seq(RevertInfo("ADD", fh.getNewPath, applied))
} else Nil } else Nil
} }
case DiffEntry.ChangeType.ADD => { case DiffEntry.ChangeType.ADD => {
val applied = PatchUtil.apply("", patch, fh) val applied = PatchUtil.apply("", patch, fh)
if(applied != null){ if(applied != null){
Seq(RevertInfo("ADD", fh.getNewPath, applied)) Seq(RevertInfo("ADD", fh.getNewPath, applied))
} else Nil } else Nil
} }
case DiffEntry.ChangeType.DELETE => { case DiffEntry.ChangeType.DELETE => {
Seq(RevertInfo("DELETE", fh.getNewPath, "")) Seq(RevertInfo("DELETE", fh.getNewPath, ""))
} }
case DiffEntry.ChangeType.RENAME => { case DiffEntry.ChangeType.RENAME => {
val applied = PatchUtil.apply("", patch, fh) val applied = PatchUtil.apply("", patch, fh)
if(applied != null){ if(applied != null){
Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied)) Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied))
} else { } else {
Seq(RevertInfo("DELETE", fh.getOldPath, "")) Seq(RevertInfo("DELETE", fh.getOldPath, ""))
} }
} }
case _ => Nil case _ => Nil
} }
}).flatten }).flatten
if(revertInfo.nonEmpty){ if(revertInfo.nonEmpty){
val builder = DirCache.newInCore.builder() val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter() val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
JGitUtil.processTree(git, headId){ (path, tree) => JGitUtil.processTree(git, headId){ (path, tree) =>
if(revertInfo.find(x => x.filePath == path).isEmpty){ if(revertInfo.find(x => x.filePath == path).isEmpty){
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
} }
} }
revertInfo.filter(_.operation == "ADD").foreach { x => revertInfo.filter(_.operation == "ADD").foreach { x =>
builder.add(JGitUtil.createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8")))) builder.add(JGitUtil.createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8"))))
} }
builder.finish() builder.finish()
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
pageName match { pageName match {
case Some(x) => s"Revert ${from} ... ${to} on ${x}" case Some(x) => s"Revert ${from} ... ${to} on ${x}"
case None => s"Revert ${from} ... ${to}" case None => s"Revert ${from} ... ${to}"
}) })
} }
} }
} }
true true
} catch { } catch {
case e: Exception => { case e: Exception => {
e.printStackTrace() e.printStackTrace()
false false
} }
} }
} }
/** /**
* Save the wiki page. * Save the wiki page.
*/ */
def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String, def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = { content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = {
LockUtil.lock(s"${owner}/${repository}/wiki"){ LockUtil.lock(s"${owner}/${repository}/wiki"){
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
val builder = DirCache.newInCore.builder() val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter() val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
var created = true var created = true
var updated = false var updated = false
var removed = false var removed = false
if(headId != null){ if(headId != null){
JGitUtil.processTree(git, headId){ (path, tree) => JGitUtil.processTree(git, headId){ (path, tree) =>
if(path == currentPageName + ".md" && currentPageName != newPageName){ if(path == currentPageName + ".md" && currentPageName != newPageName){
removed = true removed = true
} else if(path != newPageName + ".md"){ } else if(path != newPageName + ".md"){
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
} else { } else {
created = false created = false
updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false)
} }
} }
} }
if(created || updated || removed){ if(created || updated || removed){
builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
builder.finish() builder.finish()
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress, val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
if(message.trim.length == 0) { if(message.trim.length == 0) {
if(removed){ if(removed){
s"Rename ${currentPageName} to ${newPageName}" s"Rename ${currentPageName} to ${newPageName}"
} else if(created){ } else if(created){
s"Created ${newPageName}" s"Created ${newPageName}"
} else { } else {
s"Updated ${newPageName}" s"Updated ${newPageName}"
} }
} else { } else {
message message
}) })
Some(newHeadId.getName) Some(newHeadId.getName)
} else None } else None
} }
} }
} }
/** /**
* Delete the wiki page. * Delete the wiki page.
*/ */
def deleteWikiPage(owner: String, repository: String, pageName: String, def deleteWikiPage(owner: String, repository: String, pageName: String,
committer: String, mailAddress: String, message: String): Unit = { committer: String, mailAddress: String, message: String): Unit = {
LockUtil.lock(s"${owner}/${repository}/wiki"){ LockUtil.lock(s"${owner}/${repository}/wiki"){
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
val builder = DirCache.newInCore.builder() val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter() val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
var removed = false var removed = false
JGitUtil.processTree(git, headId){ (path, tree) => JGitUtil.processTree(git, headId){ (path, tree) =>
if(path != pageName + ".md"){ if(path != pageName + ".md"){
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
} else { } else {
removed = true removed = true
} }
} }
if(removed){ if(removed){
builder.finish() builder.finish()
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message) JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message)
} }
} }
} }
} }
} }

View File

@@ -1,193 +1,193 @@
package servlet package servlet
import java.io.File import java.io.File
import java.sql.{DriverManager, Connection} import java.sql.{DriverManager, Connection}
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import javax.servlet.{ServletContext, ServletContextListener, ServletContextEvent} import javax.servlet.{ServletContext, ServletContextListener, ServletContextEvent}
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import util.Directory._ import util.Directory._
import util.ControlUtil._ import util.ControlUtil._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import util.Directory import util.Directory
object AutoUpdate { object AutoUpdate {
/** /**
* Version of GitBucket * Version of GitBucket
* *
* @param majorVersion the major version * @param majorVersion the major version
* @param minorVersion the minor version * @param minorVersion the minor version
*/ */
case class Version(majorVersion: Int, minorVersion: Int){ case class Version(majorVersion: Int, minorVersion: Int){
private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version]) private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version])
/** /**
* Execute update/MAJOR_MINOR.sql to update schema to this version. * Execute update/MAJOR_MINOR.sql to update schema to this version.
* If corresponding SQL file does not exist, this method do nothing. * If corresponding SQL file does not exist, this method do nothing.
*/ */
def update(conn: Connection): Unit = { def update(conn: Connection): Unit = {
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql" val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in => using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in =>
if(in != null){ if(in != null){
val sql = IOUtils.toString(in, "UTF-8") val sql = IOUtils.toString(in, "UTF-8")
using(conn.createStatement()){ stmt => using(conn.createStatement()){ stmt =>
logger.debug(sqlPath + "=" + sql) logger.debug(sqlPath + "=" + sql)
stmt.executeUpdate(sql) stmt.executeUpdate(sql)
} }
} }
} }
} }
/** /**
* MAJOR.MINOR * MAJOR.MINOR
*/ */
val versionString = s"${majorVersion}.${minorVersion}" val versionString = s"${majorVersion}.${minorVersion}"
} }
/** /**
* 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, 0){ new Version(2, 0){
override def update(conn: Connection): Unit = { override def update(conn: Connection): Unit = {
import eu.medsea.mimeutil.{MimeUtil2, MimeType} import eu.medsea.mimeutil.{MimeUtil2, MimeType}
val mimeUtil = new MimeUtil2() val mimeUtil = new MimeUtil2()
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector") mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
super.update(conn) super.update(conn)
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs => using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
while(rs.next){ while(rs.next){
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir => defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
if(dir.exists && dir.isDirectory){ if(dir.exists && dir.isDirectory){
dir.listFiles.foreach { file => dir.listFiles.foreach { file =>
if(file.getName.indexOf('.') < 0){ if(file.getName.indexOf('.') < 0){
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
if(mimeType.startsWith("image/")){ if(mimeType.startsWith("image/")){
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1))) file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
} }
} }
} }
} }
} }
} }
} }
} }
}, },
Version(1, 13), Version(1, 13),
Version(1, 12), Version(1, 12),
Version(1, 11), Version(1, 11),
Version(1, 10), Version(1, 10),
Version(1, 9), Version(1, 9),
Version(1, 8), Version(1, 8),
Version(1, 7), Version(1, 7),
Version(1, 6), Version(1, 6),
Version(1, 5), Version(1, 5),
Version(1, 4), Version(1, 4),
new Version(1, 3){ new Version(1, 3){
override def update(conn: Connection): Unit = { override def update(conn: Connection): Unit = {
super.update(conn) super.update(conn)
// Fix wiki repository configuration // Fix wiki repository configuration
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs => using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
while(rs.next){ while(rs.next){
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git => using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
defining(git.getRepository.getConfig){ config => defining(git.getRepository.getConfig){ config =>
if(!config.getBoolean("http", "receivepack", false)){ if(!config.getBoolean("http", "receivepack", false)){
config.setBoolean("http", null, "receivepack", true) config.setBoolean("http", null, "receivepack", true)
config.save config.save
} }
} }
} }
} }
} }
} }
}, },
Version(1, 2), Version(1, 2),
Version(1, 1), Version(1, 1),
Version(1, 0), Version(1, 0),
Version(0, 0) Version(0, 0)
) )
/** /**
* The head version of BitBucket. * The head version of BitBucket.
*/ */
val headVersion = versions.head val headVersion = versions.head
/** /**
* The version file (GITBUCKET_HOME/version). * The version file (GITBUCKET_HOME/version).
*/ */
lazy val versionFile = new File(GitBucketHome, "version") lazy val versionFile = new File(GitBucketHome, "version")
/** /**
* Returns the current version from the version file. * Returns the current version from the version file.
*/ */
def getCurrentVersion(): Version = { def getCurrentVersion(): Version = {
if(versionFile.exists){ if(versionFile.exists){
FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match { FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
case Array(majorVersion, minorVersion) => { case Array(majorVersion, minorVersion) => {
versions.find { v => versions.find { v =>
v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
}.getOrElse(Version(0, 0)) }.getOrElse(Version(0, 0))
} }
case _ => Version(0, 0) case _ => Version(0, 0)
} }
} else Version(0, 0) } else Version(0, 0)
} }
} }
/** /**
* Update database schema automatically in the context initializing. * Update database schema automatically in the context initializing.
*/ */
class AutoUpdateListener extends ServletContextListener { class AutoUpdateListener extends ServletContextListener {
import AutoUpdate._ import AutoUpdate._
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener]) private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
override def contextInitialized(event: ServletContextEvent): Unit = { override def contextInitialized(event: ServletContextEvent): Unit = {
val datadir = event.getServletContext.getInitParameter("gitbucket.home") val datadir = event.getServletContext.getInitParameter("gitbucket.home")
if(datadir != null){ if(datadir != null){
System.setProperty("gitbucket.home", datadir) System.setProperty("gitbucket.home", datadir)
} }
org.h2.Driver.load() org.h2.Driver.load()
event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true") event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true")
logger.debug("Start schema update") logger.debug("Start schema update")
defining(getConnection(event.getServletContext)){ conn => defining(getConnection(event.getServletContext)){ conn =>
try { try {
defining(getCurrentVersion()){ currentVersion => defining(getCurrentVersion()){ currentVersion =>
if(currentVersion == headVersion){ if(currentVersion == headVersion){
logger.debug("No update") logger.debug("No update")
} else if(!versions.contains(currentVersion)){ } else if(!versions.contains(currentVersion)){
logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.") logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.")
} else { } else {
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn)) versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8") FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
conn.commit() conn.commit()
logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}") logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
} }
} }
} catch { } catch {
case ex: Throwable => { case ex: Throwable => {
logger.error("Failed to schema update", ex) logger.error("Failed to schema update", ex)
ex.printStackTrace() ex.printStackTrace()
conn.rollback() conn.rollback()
} }
} }
} }
logger.debug("End schema update") logger.debug("End schema update")
} }
def contextDestroyed(sce: ServletContextEvent): Unit = { def contextDestroyed(sce: ServletContextEvent): Unit = {
// Nothing to do. // Nothing to do.
} }
private def getConnection(servletContext: ServletContext): Connection = private def getConnection(servletContext: ServletContext): Connection =
DriverManager.getConnection( DriverManager.getConnection(
servletContext.getInitParameter("db.url"), servletContext.getInitParameter("db.url"),
servletContext.getInitParameter("db.user"), servletContext.getInitParameter("db.user"),
servletContext.getInitParameter("db.password")) servletContext.getInitParameter("db.password"))
} }

View File

@@ -1,38 +1,38 @@
package servlet package servlet
import javax.servlet._ import javax.servlet._
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
/** /**
* Controls the transaction with the open session in view pattern. * Controls the transaction with the open session in view pattern.
*/ */
class TransactionFilter extends Filter { class TransactionFilter extends Filter {
private val logger = LoggerFactory.getLogger(classOf[TransactionFilter]) private val logger = LoggerFactory.getLogger(classOf[TransactionFilter])
def init(config: FilterConfig) = {} def init(config: FilterConfig) = {}
def destroy(): Unit = {} def destroy(): Unit = {}
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){ if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){
// assets don't need transaction // assets don't need transaction
chain.doFilter(req, res) chain.doFilter(req, res)
} else { } else {
Database(req.getServletContext) withTransaction { Database(req.getServletContext) withTransaction {
logger.debug("begin transaction") logger.debug("begin transaction")
chain.doFilter(req, res) chain.doFilter(req, res)
logger.debug("end transaction") logger.debug("end transaction")
} }
} }
} }
} }
object Database { object Database {
def apply(context: ServletContext): scala.slick.session.Database = def apply(context: ServletContext): scala.slick.session.Database =
scala.slick.session.Database.forURL(context.getInitParameter("db.url"), scala.slick.session.Database.forURL(context.getInitParameter("db.url"),
context.getInitParameter("db.user"), context.getInitParameter("db.user"),
context.getInitParameter("db.password")) context.getInitParameter("db.password"))
} }

View File

@@ -1,116 +1,116 @@
package util package util
import scala.concurrent._ import scala.concurrent._
import ExecutionContext.Implicits.global import ExecutionContext.Implicits.global
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail} import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import app.Context import app.Context
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
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)
(msg: String => String)(implicit context: Context): Unit (msg: String => String)(implicit context: Context): Unit
protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit context: Context) = protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit context: Context) =
( (
// individual repository's owner // individual repository's owner
issue.userName :: issue.userName ::
// collaborators // collaborators
getCollaborators(issue.userName, issue.repositoryName) ::: getCollaborators(issue.userName, issue.repositoryName) :::
// participants // participants
issue.openedUserName :: issue.openedUserName ::
getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName) getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
) )
.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) foreach (x => notify(x.mailAddress)) )
} }
object Notifier { object Notifier {
// TODO We want to be able to switch to mock. // TODO We want to be able to switch to mock.
def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match { def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match {
case settings if settings.notification => new Mailer(settings.smtp.get) case settings if settings.notification => new Mailer(settings.smtp.get)
case _ => new MockMailer case _ => new MockMailer
} }
def msgIssue(url: String) = (content: String) => s""" def msgIssue(url: String) = (content: String) => s"""
|${content}<br/> |${content}<br/>
|--<br/> |--<br/>
|<a href="${url}">View it on GitBucket</a> |<a href="${url}">View it on GitBucket</a>
""".stripMargin """.stripMargin
def msgPullRequest(url: String) = (content: String) => s""" def msgPullRequest(url: String) = (content: String) => s"""
|${content}<hr/> |${content}<hr/>
|View, comment on, or merge it at:<br/> |View, comment on, or merge it at:<br/>
|<a href="${url}">${url}</a> |<a href="${url}">${url}</a>
""".stripMargin """.stripMargin
def msgComment(url: String) = (content: String) => s""" def msgComment(url: String) = (content: String) => s"""
|${content}<br/> |${content}<br/>
|--<br/> |--<br/>
|<a href="${url}">View it on GitBucket</a> |<a href="${url}">View it on GitBucket</a>
""".stripMargin """.stripMargin
def msgStatus(url: String) = (content: String) => s""" def msgStatus(url: String) = (content: String) => s"""
|${content} <a href="${url}">#${url split('/') last}</a> |${content} <a href="${url}">#${url split('/') last}</a>
""".stripMargin """.stripMargin
} }
class Mailer(private val smtp: Smtp) extends Notifier { class Mailer(private val smtp: Smtp) extends Notifier {
private val logger = LoggerFactory.getLogger(classOf[Mailer]) private val logger = LoggerFactory.getLogger(classOf[Mailer])
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
(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? // TODO Can we use the Database Session in other than Transaction Filter?
database withSession { database withSession {
getIssue(r.owner, r.name, issueId.toString) foreach { issue => getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
defining( defining(
s"[${r.name}] ${issue.title} (#${issueId})" -> s"[${r.name}] ${issue.title} (#${issueId})" ->
msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) => msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
recipients(issue) { to => recipients(issue) { to =>
val email = new HtmlEmail val email = new HtmlEmail
email.setHostName(smtp.host) email.setHostName(smtp.host)
email.setSmtpPort(smtp.port.get) email.setSmtpPort(smtp.port.get)
smtp.user.foreach { user => smtp.user.foreach { user =>
email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse(""))) email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
} }
smtp.ssl.foreach { ssl => smtp.ssl.foreach { ssl =>
email.setSSLOnConnect(ssl) email.setSSLOnConnect(ssl)
} }
smtp.fromAddress smtp.fromAddress
.map (_ -> smtp.fromName.orNull) .map (_ -> smtp.fromName.orNull)
.orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName)) .orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
.foreach { case (address, name) => .foreach { case (address, name) =>
email.setFrom(address, name) email.setFrom(address, name)
} }
email.setCharset("UTF-8") email.setCharset("UTF-8")
email.setSubject(subject) email.setSubject(subject)
email.setHtmlMsg(msg) email.setHtmlMsg(msg)
email.addTo(to).send email.addTo(to).send
} }
} }
} }
} }
"Notifications Successful." "Notifications Successful."
} }
f onSuccess { f onSuccess {
case s => logger.debug(s) case s => logger.debug(s)
} }
f onFailure { f onFailure {
case t => logger.error("Notifications Failed.", t) case t => logger.error("Notifications Failed.", t)
} }
} }
} }
class MockMailer extends Notifier { class MockMailer extends Notifier {
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
(msg: String => String)(implicit context: Context): Unit = {} (msg: String => String)(implicit context: Context): Unit = {}
} }

View File

@@ -1,71 +1,71 @@
@(users: List[model.Account], members: Map[String, List[String]], includeRemoved: Boolean)(implicit context: app.Context) @(users: List[model.Account], members: Map[String, List[String]], includeRemoved: Boolean)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main("Manage Users"){ @html.main("Manage Users"){
@admin.html.menu("users"){ @admin.html.menu("users"){
<div class="pull-right" style="margin-bottom: 4px;"> <div class="pull-right" style="margin-bottom: 4px;">
<a href="@path/admin/users/_newuser" class="btn">New User</a> <a href="@path/admin/users/_newuser" class="btn">New User</a>
<a href="@path/admin/users/_newgroup" class="btn">New Group</a> <a href="@path/admin/users/_newgroup" class="btn">New Group</a>
</div> </div>
<label for="includeRemoved"> <label for="includeRemoved">
<input type="checkbox" id="includeRemoved" name="includeRemoved" @if(includeRemoved){checked}/> <input type="checkbox" id="includeRemoved" name="includeRemoved" @if(includeRemoved){checked}/>
Include removed users Include removed users
</label> </label>
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
@users.map { account => @users.map { account =>
<tr> <tr>
<td @if(account.isRemoved){style="background-color: #dddddd;"}> <td @if(account.isRemoved){style="background-color: #dddddd;"}>
<div class="pull-right"> <div class="pull-right">
@if(account.isGroupAccount){ @if(account.isGroupAccount){
<a href="@path/admin/users/@account.userName/_editgroup">Edit</a> <a href="@path/admin/users/@account.userName/_editgroup">Edit</a>
} else { } else {
<a href="@path/admin/users/@account.userName/_edituser">Edit</a> <a href="@path/admin/users/@account.userName/_edituser">Edit</a>
} }
</div> </div>
<div class="strong"> <div class="strong">
@avatar(account.userName, 20) @avatar(account.userName, 20)
<a href="@url(account.userName)">@account.userName</a> <a href="@url(account.userName)">@account.userName</a>
@if(account.isGroupAccount){ @if(account.isGroupAccount){
(Group) (Group)
} else { } else {
@if(account.isAdmin){ @if(account.isAdmin){
(Administrator) (Administrator)
} else { } else {
(Normal) (Normal)
} }
} }
@if(account.isGroupAccount){ @if(account.isGroupAccount){
@members(account.userName).map { userName => @members(account.userName).map { userName =>
@avatar(userName, 20, tooltip = true) @avatar(userName, 20, tooltip = true)
} }
} }
</div> </div>
<div> <div>
<hr> <hr>
@if(!account.isGroupAccount){ @if(!account.isGroupAccount){
<i class="icon-envelope"></i> @account.mailAddress <i class="icon-envelope"></i> @account.mailAddress
} }
@account.url.map { url => @account.url.map { url =>
<i class="icon-home"></i> @url <i class="icon-home"></i> @url
} }
</div> </div>
<div> <div>
<span class="muted">Registered:</span> @datetime(account.registeredDate) <span class="muted">Registered:</span> @datetime(account.registeredDate)
<span class="muted">Updated:</span> @datetime(account.updatedDate) <span class="muted">Updated:</span> @datetime(account.updatedDate)
@if(!account.isGroupAccount){ @if(!account.isGroupAccount){
<span class="muted">Last Login:</span> @account.lastLoginDate.map(datetime) <span class="muted">Last Login:</span> @account.lastLoginDate.map(datetime)
} }
</div> </div>
</td> </td>
</tr> </tr>
} }
</table> </table>
} }
} }
<script> <script>
$(function(){ $(function(){
$('#includeRemoved').click(function(){ $('#includeRemoved').click(function(){
location.href = '@path/admin/users?includeRemoved=' + this.checked; location.href = '@path/admin/users?includeRemoved=' + this.checked;
}); });
}); });
</script> </script>

View File

@@ -1,80 +1,80 @@
@(account: Option[model.Account])(implicit context: app.Context) @(account: Option[model.Account])(implicit context: app.Context)
@import context._ @import context._
@html.main(if(account.isEmpty) "New User" else "Update User"){ @html.main(if(account.isEmpty) "New User" else "Update User"){
@admin.html.menu("users"){ @admin.html.menu("users"){
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newuser} else {@path/admin/users/@account.get.userName/_edituser}" validate="true"> <form method="POST" action="@if(account.isEmpty){@path/admin/users/_newuser} else {@path/admin/users/@account.get.userName/_edituser}" validate="true">
<div class="row-fluid"> <div class="row-fluid">
<div class="span6"> <div class="span6">
<fieldset> <fieldset>
<label for="userName" class="strong">Username:</label> <label for="userName" class="strong">Username:</label>
<div> <div>
<span id="error-userName" class="error"></span> <span id="error-userName" class="error"></span>
</div> </div>
<input type="text" name="userName" id="userName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/> <input type="text" name="userName" id="userName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
@if(account.isDefined){ @if(account.isDefined){
<label for="removed"> <label for="removed">
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/> <input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/>
Disable Disable
</label> </label>
} }
</fieldset> </fieldset>
@if(account.map(_.password.nonEmpty).getOrElse(true)){ @if(account.map(_.password.nonEmpty).getOrElse(true)){
<fieldset> <fieldset>
<label for="password" class="strong"> <label for="password" class="strong">
Password Password
@if(account.isDefined){ @if(account.isDefined){
(Input to change password) (Input to change password)
} }
: :
</label> </label>
<div> <div>
<span id="error-password" class="error"></span> <span id="error-password" class="error"></span>
</div> </div>
<input type="password" name="password" id="password" value="" autocomplete="off"/> <input type="password" name="password" id="password" value="" autocomplete="off"/>
</fieldset> </fieldset>
} }
<fieldset> <fieldset>
<label for="fullName" class="strong">Full Name:</label> <label for="fullName" class="strong">Full Name:</label>
<div> <div>
<span id="error-fullName" class="error"></span> <span id="error-fullName" class="error"></span>
</div> </div>
<input type="text" name="fullName" id="fullName" value="@account.map(_.fullName)"/> <input type="text" name="fullName" id="fullName" value="@account.map(_.fullName)"/>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="mailAddress" class="strong">Mail Address:</label> <label for="mailAddress" class="strong">Mail Address:</label>
<div> <div>
<span id="error-mailAddress" class="error"></span> <span id="error-mailAddress" class="error"></span>
</div> </div>
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/> <input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label class="strong">User Type:</label> <label class="strong">User Type:</label>
<label class="radio" for="userType_Normal"> <label class="radio" for="userType_Normal">
<input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal <input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal
</label> </label>
<label class="radio" for="userType_Admin"> <label class="radio" for="userType_Admin">
<input type="radio" name="isAdmin" id="userType_Admin" value="true"@if(account.isDefined && account.get.isAdmin){ checked}/> Administrator <input type="radio" name="isAdmin" id="userType_Admin" value="true"@if(account.isDefined && account.get.isAdmin){ checked}/> Administrator
</label> </label>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label class="strong">URL (Optional):</label> <label class="strong">URL (Optional):</label>
<div> <div>
<span id="error-url" class="error"></span> <span id="error-url" class="error"></span>
</div> </div>
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/> <input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
</fieldset> </fieldset>
</div> </div>
<div class="span6"> <div class="span6">
<fieldset> <fieldset>
<label for="avatar" class="strong">Image (Optional)</label> <label for="avatar" class="strong">Image (Optional)</label>
@helper.html.uploadavatar(account) @helper.html.uploadavatar(account)
</fieldset> </fieldset>
</div> </div>
</div> </div>
<fieldset class="margin"> <fieldset class="margin">
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/> <input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/>
<a href="@path/admin/users" class="btn">Cancel</a> <a href="@path/admin/users" class="btn">Cancel</a>
</fieldset> </fieldset>
</form> </form>
} }
} }

View File

@@ -1,98 +1,98 @@
@(activities: List[model.Activity])(implicit context: app.Context) @(activities: List[model.Activity])(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@if(activities.isEmpty){ @if(activities.isEmpty){
No activity No activity
} else { } else {
@activities.map { activity => @activities.map { activity =>
<div class="block"> <div class="block">
@(activity.activityType match { @(activity.activityType match {
case "open_issue" => detailActivity(activity, "activity-issue.png") case "open_issue" => detailActivity(activity, "activity-issue.png")
case "comment_issue" => detailActivity(activity, "activity-comment.png") case "comment_issue" => detailActivity(activity, "activity-comment.png")
case "close_issue" => detailActivity(activity, "activity-issue-close.png") case "close_issue" => detailActivity(activity, "activity-issue-close.png")
case "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png") case "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png")
case "open_pullreq" => detailActivity(activity, "activity-merge.png") case "open_pullreq" => detailActivity(activity, "activity-merge.png")
case "merge_pullreq" => detailActivity(activity, "activity-merge.png") case "merge_pullreq" => detailActivity(activity, "activity-merge.png")
case "create_repository" => simpleActivity(activity, "activity-create-repository.png") case "create_repository" => simpleActivity(activity, "activity-create-repository.png")
case "create_branch" => simpleActivity(activity, "activity-branch.png") case "create_branch" => simpleActivity(activity, "activity-branch.png")
case "delete_branch" => simpleActivity(activity, "activity-delete.png") case "delete_branch" => simpleActivity(activity, "activity-delete.png")
case "create_tag" => simpleActivity(activity, "activity-tag.png") case "create_tag" => simpleActivity(activity, "activity-tag.png")
case "delete_tag" => simpleActivity(activity, "activity-delete.png") case "delete_tag" => simpleActivity(activity, "activity-delete.png")
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.take(4).zipWithIndex.map{ case (commit, i) =>
if(i == 3){ if(i == 3){
<div>...</div> <div>...</div>
} else { } else {
if(commit.nonEmpty){ if(commit.nonEmpty){
<div> <div>
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/commit/${commit. substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a> <a href={s"${path}/${activity.userName}/${activity.repositoryName}/commit/${commit. substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
<span>{commit.substring(41)}</span> <span>{commit.substring(41)}</span>
</div> </div>
} }
} }
}} }}
</div> </div>
} }
case "create_wiki" => customActivity(activity, "activity-wiki.png"){ case "create_wiki" => customActivity(activity, "activity-wiki.png"){
<div class="small activity-message"> <div class="small activity-message">
Created <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>. Created <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>.
</div> </div>
} }
case "edit_wiki" => customActivity(activity, "activity-wiki.png"){ case "edit_wiki" => customActivity(activity, "activity-wiki.png"){
activity.additionalInfo.get.split(":") match { activity.additionalInfo.get.split(":") match {
case Array(pageName, commitId) => case Array(pageName, commitId) =>
<div class="small activity-message"> <div class="small activity-message">
Edited <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>. Edited <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}/_compare/${commitId.substring(0, 7)}^...${commitId.substring(0, 7)}"}>View the diff »</a> <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}/_compare/${commitId.substring(0, 7)}^...${commitId.substring(0, 7)}"}>View the diff »</a>
</div> </div>
case Array(pageName) => case Array(pageName) =>
<div class="small activity-message"> <div class="small activity-message">
Edited <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>. Edited <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
</div> </div>
} }
} }
}) })
</div> </div>
} }
} }
@detailActivity(activity: model.Activity, image: String) = { @detailActivity(activity: model.Activity, image: String) = {
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div> <div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
<div class="activity-content"> <div class="activity-content">
<div class="muted small">@datetime(activity.activityDate)</div> <div class="muted small">@datetime(activity.activityDate)</div>
<div class="strong"> <div class="strong">
@avatar(activity.activityUserName, 16) @avatar(activity.activityUserName, 16)
@activityMessage(activity.message) @activityMessage(activity.message)
</div> </div>
@activity.additionalInfo.map { additionalInfo => @activity.additionalInfo.map { additionalInfo =>
<div class=" activity-message">@additionalInfo</div> <div class=" activity-message">@additionalInfo</div>
} }
</div> </div>
} }
@customActivity(activity: model.Activity, image: String)(additionalInfo: Any) = { @customActivity(activity: model.Activity, image: String)(additionalInfo: Any) = {
<div class="activity-icon-large"><img src="@assets/common/images/@image"/></div> <div class="activity-icon-large"><img src="@assets/common/images/@image"/></div>
<div class="activity-content"> <div class="activity-content">
<div class="muted small">@datetime(activity.activityDate)</div> <div class="muted small">@datetime(activity.activityDate)</div>
<div class="strong"> <div class="strong">
@avatar(activity.activityUserName, 16) @avatar(activity.activityUserName, 16)
@activityMessage(activity.message) @activityMessage(activity.message)
</div> </div>
@additionalInfo @additionalInfo
</div> </div>
} }
@simpleActivity(activity: model.Activity, image: String) = { @simpleActivity(activity: model.Activity, image: String) = {
<div class="activity-icon-small"><img src="@assets/common/images/@image"/></div> <div class="activity-icon-small"><img src="@assets/common/images/@image"/></div>
<div class="activity-content"> <div class="activity-content">
<div> <div>
@avatar(activity.activityUserName, 16) @avatar(activity.activityUserName, 16)
@activityMessage(activity.message) @activityMessage(activity.message)
<span class="muted small">@datetime(activity.activityDate)</span> <span class="muted small">@datetime(activity.activityDate)</span>
</div> </div>
</div> </div>
} }

View File

@@ -1,105 +1,105 @@
@(diffs: Seq[util.JGitUtil.DiffInfo], @(diffs: Seq[util.JGitUtil.DiffInfo],
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
newCommitId: Option[String], newCommitId: Option[String],
oldCommitId: Option[String], oldCommitId: Option[String],
showIndex: Boolean)(implicit context: app.Context) showIndex: Boolean)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import org.eclipse.jgit.diff.DiffEntry.ChangeType @import org.eclipse.jgit.diff.DiffEntry.ChangeType
@if(showIndex){ @if(showIndex){
<div> <div>
<div class="pull-right" style="margin-bottom: 10px;"> <div class="pull-right" style="margin-bottom: 10px;">
<input id="toggle-file-list" type="button" class="btn" value="Show file list"/> <input id="toggle-file-list" type="button" class="btn" value="Show file list"/>
</div> </div>
Showing @diffs.size changed @plural(diffs.size, "file") Showing @diffs.size changed @plural(diffs.size, "file")
</div> </div>
<ul id="commit-file-list" style="display: none;"> <ul id="commit-file-list" style="display: none;">
@diffs.zipWithIndex.map { case (diff, i) => @diffs.zipWithIndex.map { case (diff, i) =>
<li@if(i > 0){ class="border"}> <li@if(i > 0){ class="border"}>
<a href="#diff-@i"> <a href="#diff-@i">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){ @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath <img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
} }
@if(diff.changeType == ChangeType.ADD){ @if(diff.changeType == ChangeType.ADD){
<img src="@assets/common/images/diff_add.png"/> @diff.newPath <img src="@assets/common/images/diff_add.png"/> @diff.newPath
} }
@if(diff.changeType == ChangeType.MODIFY){ @if(diff.changeType == ChangeType.MODIFY){
<img src="@assets/common/images/diff_edit.png"/> @diff.newPath <img src="@assets/common/images/diff_edit.png"/> @diff.newPath
} }
@if(diff.changeType == ChangeType.DELETE){ @if(diff.changeType == ChangeType.DELETE){
<img src="@assets/common/images/diff_delete.png"/> @diff.oldPath <img src="@assets/common/images/diff_delete.png"/> @diff.oldPath
} }
</a> </a>
</li> </li>
} }
</ul> </ul>
} }
@diffs.zipWithIndex.map { case (diff, i) => @diffs.zipWithIndex.map { case (diff, i) =>
<a name="diff-@i"></a> <a name="diff-@i"></a>
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<th style="font-weight: normal;" class="box-header"> <th style="font-weight: normal;" class="box-header">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){ @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
@diff.oldPath -> @diff.newPath @diff.oldPath -> @diff.newPath
@if(newCommitId.isDefined){ @if(newCommitId.isDefined){
<div class="pull-right align-right"> <div class="pull-right align-right">
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small">View file @@ @newCommitId.get.substring(0, 10)</a> <a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small">View file @@ @newCommitId.get.substring(0, 10)</a>
</div> </div>
} }
} }
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){ @if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
@diff.newPath @diff.newPath
@if(newCommitId.isDefined){ @if(newCommitId.isDefined){
<div class="pull-right align-right"> <div class="pull-right align-right">
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small">View file @@ @newCommitId.get.substring(0, 10)</a> <a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small">View file @@ @newCommitId.get.substring(0, 10)</a>
</div> </div>
} }
} }
@if(diff.changeType == ChangeType.DELETE){ @if(diff.changeType == ChangeType.DELETE){
@diff.oldPath @diff.oldPath
@if(oldCommitId.isDefined){ @if(oldCommitId.isDefined){
<div class="pull-right align-right"> <div class="pull-right align-right">
<a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-small">View file @@ @oldCommitId.get.substring(0, 10)</a> <a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-small">View file @@ @oldCommitId.get.substring(0, 10)</a>
</div> </div>
} }
} }
</th> </th>
</tr> </tr>
<tr> <tr>
<td> <td>
@if(diff.newContent != None || diff.oldContent != None){ @if(diff.newContent != None || diff.oldContent != None){
<div id="diffText-@i"></div> <div id="diffText-@i"></div>
<textarea id="newText-@i" style="display: none;">@diff.newContent.getOrElse("")</textarea> <textarea id="newText-@i" style="display: none;">@diff.newContent.getOrElse("")</textarea>
<textarea id="oldText-@i" style="display: none;">@diff.oldContent.getOrElse("")</textarea> <textarea id="oldText-@i" style="display: none;">@diff.oldContent.getOrElse("")</textarea>
} else { } else {
Not supported Not supported
} }
</td> </td>
</tr> </tr>
</table> </table>
} }
<script type="text/javascript" src="@assets/vendors/jsdifflib/difflib.js"></script> <script type="text/javascript" src="@assets/vendors/jsdifflib/difflib.js"></script>
<script type="text/javascript" src="@assets/vendors/jsdifflib/diffview.js"></script> <script type="text/javascript" src="@assets/vendors/jsdifflib/diffview.js"></script>
<link href="@assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" /> <link href="@assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script> <script>
$(function(){ $(function(){
@if(showIndex){ @if(showIndex){
$('#toggle-file-list').click(function(){ $('#toggle-file-list').click(function(){
$('#commit-file-list').toggle(); $('#commit-file-list').toggle();
if($(this).val() == 'Show file list'){ if($(this).val() == 'Show file list'){
$(this).val('Hide file list'); $(this).val('Hide file list');
} else { } else {
$(this).val('Show file list'); $(this).val('Show file list');
} }
}); });
} }
@diffs.zipWithIndex.map { case (diff, i) => @diffs.zipWithIndex.map { case (diff, i) =>
@if(diff.newContent != None || diff.oldContent != None){ @if(diff.newContent != None || diff.oldContent != None){
if($('#oldText-@i').length > 0){ if($('#oldText-@i').length > 0){
diffUsingJS('oldText-@i', 'newText-@i', 'diffText-@i'); diffUsingJS('oldText-@i', 'newText-@i', 'diffText-@i');
} }
} }
} }
}); });
</script> </script>

View File

@@ -1,70 +1,70 @@
@(activities: List[model.Activity], @(activities: List[model.Activity],
recentRepositories: List[service.RepositoryService.RepositoryInfo], recentRepositories: List[service.RepositoryService.RepositoryInfo],
userRepositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context) userRepositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@main("GitBucket"){ @main("GitBucket"){
<div class="container"> <div class="container">
@dashboard.html.tab() @dashboard.html.tab()
<div class="row-fluid"> <div class="row-fluid">
<div class="span8"> <div class="span8">
@helper.html.activities(activities) @helper.html.activities(activities)
</div> </div>
<div class="span4"> <div class="span4">
@if(loginAccount.isEmpty){ @if(loginAccount.isEmpty){
@signinform(settings) @signinform(settings)
} else { } else {
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<th class="metal"> <th class="metal">
<div class="pull-right"> <div class="pull-right">
<a href="@path/new" class="btn btn-success btn-mini">New repository</a> <a href="@path/new" class="btn btn-success btn-mini">New repository</a>
</div> </div>
Your repositories (@userRepositories.size) Your repositories (@userRepositories.size)
</th> </th>
</tr> </tr>
@if(userRepositories.isEmpty){ @if(userRepositories.isEmpty){
<tr> <tr>
<td>No repositories</td> <td>No repositories</td>
</tr> </tr>
} else { } else {
@userRepositories.map { repository => @userRepositories.map { repository =>
<tr> <tr>
<td> <td>
@helper.html.repositoryicon(repository, false) @helper.html.repositoryicon(repository, false)
@if(repository.owner == loginAccount.get.userName){ @if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)"><span class="strong">@repository.name</span></a> <a href="@url(repository)"><span class="strong">@repository.name</span></a>
} else { } else {
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a> <a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
} }
</td> </td>
</tr> </tr>
} }
} }
</table> </table>
} }
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<th class="metal"> <th class="metal">
Recent updated repositories Recent updated repositories
</th> </th>
</tr> </tr>
@if(recentRepositories.isEmpty){ @if(recentRepositories.isEmpty){
<tr> <tr>
<td>No repositories</td> <td>No repositories</td>
</tr> </tr>
} else { } else {
@recentRepositories.map { repository => @recentRepositories.map { repository =>
<tr> <tr>
<td> <td>
@helper.html.repositoryicon(repository, false) @helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a> <a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</td> </td>
</tr> </tr>
} }
} }
</table> </table>
</div> </div>
</div> </div>
</div> </div>
} }

View File

@@ -1,147 +1,147 @@
@(collaborators: List[String], @(collaborators: List[String],
milestones: List[model.Milestone], milestones: List[model.Milestone],
labels: List[model.Label], labels: List[model.Label],
hasWritePermission: Boolean, hasWritePermission: Boolean,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("issues", repository){ @html.menu("issues", repository){
@tab("", true, repository) @tab("", true, repository)
<form action="@url(repository)/issues/new" method="POST" validate="true"> <form action="@url(repository)/issues/new" method="POST" validate="true">
<div class="row-fluid"> <div class="row-fluid">
<div class="span9"> <div class="span9">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div> <div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="box issue-box"> <div class="box issue-box">
<div class="box-content"> <div class="box-content">
<span id="error-title" class="error"></span> <span id="error-title" class="error"></span>
<input type="text" name="title" value="" placeholder="Title" style="width: 565px;"/> <input type="text" name="title" value="" placeholder="Title" style="width: 565px;"/>
<div> <div>
<span id="label-assigned">No one is assigned</span> <span id="label-assigned">No one is assigned</span>
@if(hasWritePermission){ @if(hasWritePermission){
<input type="hidden" name="assignedUserName" value=""/> <input type="hidden" name="assignedUserName" value=""/>
@helper.html.dropdown() { @helper.html.dropdown() {
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li> <li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
@collaborators.map { collaborator => @collaborators.map { collaborator =>
<li><a href="javascript:void(0);" class="assign" data-name="@collaborator"><i class="icon-while"></i>@avatar(collaborator, 20) @collaborator</a></li> <li><a href="javascript:void(0);" class="assign" data-name="@collaborator"><i class="icon-while"></i>@avatar(collaborator, 20) @collaborator</a></li>
} }
} }
} }
<div class="pull-right"> <div class="pull-right">
<span id="label-milestone">No milestone</span> <span id="label-milestone">No milestone</span>
@if(hasWritePermission){ @if(hasWritePermission){
<input type="hidden" name="milestoneId" value=""/> <input type="hidden" name="milestoneId" value=""/>
@helper.html.dropdown() { @helper.html.dropdown() {
<li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> No milestone</a></li> <li><a href="javascript:void(0);" class="milestone" data-id=""><i class="icon-remove-circle"></i> No milestone</a></li>
@milestones.filter(_.closedDate.isEmpty).map { milestone => @milestones.filter(_.closedDate.isEmpty).map { milestone =>
<li> <li>
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title"> <a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
<i class="icon-while"></i> @milestone.title <i class="icon-while"></i> @milestone.title
<div class="small" style="padding-left: 20px;"> <div class="small" style="padding-left: 20px;">
@milestone.dueDate.map { dueDate => @milestone.dueDate.map { dueDate =>
@if(isPast(dueDate)){ @if(isPast(dueDate)){
<img src="@assets/common/images/alert_mono.png"/>Due in @date(dueDate) <img src="@assets/common/images/alert_mono.png"/>Due in @date(dueDate)
} else { } else {
<span class="muted">Due in @date(dueDate)</span> <span class="muted">Due in @date(dueDate)</span>
} }
}.getOrElse { }.getOrElse {
<span class="muted">No due date</span> <span class="muted">No due date</span>
} }
</div> </div>
</a> </a>
</li> </li>
} }
} }
} }
</div> </div>
</div> </div>
<hr> <hr>
@helper.html.preview(repository, "", false, true, "width: 565px; height: 200px; max-height: 250px;", elastic = true) @helper.html.preview(repository, "", false, true, "width: 565px; height: 200px; max-height: 250px;", elastic = true)
</div> </div>
</div> </div>
<div class="pull-right"> <div class="pull-right">
<input type="submit" class="btn btn-success" value="Submit new issue"/> <input type="submit" class="btn btn-success" value="Submit new issue"/>
</div> </div>
</div> </div>
<div class="span3"> <div class="span3">
@if(hasWritePermission){ @if(hasWritePermission){
<span class="strong">Add Labels</span> <span class="strong">Add Labels</span>
<div> <div>
<div id="label-list"> <div id="label-list">
<ul class="label-list nav nav-pills nav-stacked"> <ul class="label-list nav nav-pills nav-stacked">
@labels.map { label => @labels.map { label =>
<li> <li>
<a href="javascript:void(0);" class="toggle-label" data-label="@label.labelName" data-bgcolor="@label.color" data-fgcolor="@label.fontColor"> <a href="javascript:void(0);" class="toggle-label" data-label="@label.labelName" data-bgcolor="@label.color" data-fgcolor="@label.fontColor">
<span style="background-color: #@label.color;" class="label-color">&nbsp;&nbsp;</span> <span style="background-color: #@label.color;" class="label-color">&nbsp;&nbsp;</span>
@label.labelName @label.labelName
</a> </a>
</li> </li>
} }
</ul> </ul>
<input type="hidden" name="labelNames" value=""/> <input type="hidden" name="labelNames" value=""/>
</div> </div>
</div> </div>
} }
</div> </div>
</div> </div>
</form> </form>
} }
} }
<script> <script>
$(function(){ $(function(){
$('a.assign').click(function(){ $('a.assign').click(function(){
var userName = $(this).data('name'); var userName = $(this).data('name');
$('a.assign i.icon-ok').attr('class', 'icon-white'); $('a.assign i.icon-ok').attr('class', 'icon-white');
if(userName == ''){ if(userName == ''){
$('#label-assigned').text('No one will be assigned'); $('#label-assigned').text('No one will be assigned');
} else { } else {
$('#label-assigned').html($('<span>') $('#label-assigned').html($('<span>')
.append($('<a class="username strong">').attr('href', '@path/' + userName).text(userName)) .append($('<a class="username strong">').attr('href', '@path/' + userName).text(userName))
.append(' will be assigned')); .append(' will be assigned'));
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok'); $('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok');
} }
$('input[name=assignedUserName]').val(userName); $('input[name=assignedUserName]').val(userName);
}); });
$('a.milestone').click(function(){ $('a.milestone').click(function(){
var title = $(this).data('title'); var title = $(this).data('title');
var milestoneId = $(this).data('id'); var milestoneId = $(this).data('id');
$('a.milestone i.icon-ok').attr('class', 'icon-white'); $('a.milestone i.icon-ok').attr('class', 'icon-white');
if(milestoneId == ''){ if(milestoneId == ''){
$('#label-milestone').text('No milestone'); $('#label-milestone').text('No milestone');
} else { } else {
$('#label-milestone').html($('<span>').append('Milestone: ').append($('<span class="strong">').text(title))); $('#label-milestone').html($('<span>').append('Milestone: ').append($('<span class="strong">').text(title)));
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok'); $('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
} }
$('input[name=milestoneId]').val(milestoneId); $('input[name=milestoneId]').val(milestoneId);
}); });
$('a.toggle-label').click(function(){ $('a.toggle-label').click(function(){
if($(this).data('selected') == true){ if($(this).data('selected') == true){
$(this).css({ $(this).css({
'background-color': 'white', 'background-color': 'white',
'color' : 'black', 'color' : 'black',
'font-weight' : 'normal' 'font-weight' : 'normal'
}); });
$(this).data('selected', false); $(this).data('selected', false);
} else { } else {
$(this).css({ $(this).css({
'background-color': '#' + $(this).data('bgcolor'), 'background-color': '#' + $(this).data('bgcolor'),
'color' : '#' + $(this).data('fgcolor'), 'color' : '#' + $(this).data('fgcolor'),
'font-weight' : 'bold' 'font-weight' : 'bold'
}); });
$(this).data('selected', true); $(this).data('selected', true);
} }
var labelNames = Array(); var labelNames = Array();
$('a.toggle-label').each(function(i, e){ $('a.toggle-label').each(function(i, e){
if($(e).data('selected') == true){ if($(e).data('selected') == true){
labelNames.push($(e).data('label')); labelNames.push($(e).data('label'));
} }
}); });
$('input[name=labelNames]').val(labelNames.join(',')); $('input[name=labelNames]').val(labelNames.join(','));
}); });
}); });
</script> </script>

View File

@@ -1,25 +1,25 @@
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]], @(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
<div class="box"> <div class="box">
<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.time)</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.committer, commit.mailAddress, "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;">
<a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a> <a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a>
</td> </td>
</tr> </tr>
} }
} }
</table> </table>
</div> </div>

View File

@@ -1,24 +1,24 @@
@(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) @(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("code", repository){
<h1>Tags</h1> <h1>Tags</h1>
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<th width="40%">Tag</th> <th width="40%">Tag</th>
<th width="20%">Date</th> <th width="20%">Date</th>
<th width="20%">Commit</th> <th width="20%">Commit</th>
<th width="20%">Download</th> <th width="20%">Download</th>
</tr> </tr>
@repository.tags.map { tag => @repository.tags.map { tag =>
<tr> <tr>
<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></td>
</tr> </tr>
} }
</table> </table>
} }
} }

View File

@@ -1,38 +1,38 @@
@(active: String, fileCount: Int, issueCount: Int, query: String, @(active: String, fileCount: Int, issueCount: Int, query: String,
repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.menu("", repository){ @html.menu("", repository){
<div class="row-fluid"> <div class="row-fluid">
<div class="span3"> <div class="span3">
<div class="box"> <div class="box">
<ul class="nav nav-tabs nav-stacked side-menu"> <ul class="nav nav-tabs nav-stacked side-menu">
<li@if(active=="code"){ class="active"}> <li@if(active=="code"){ class="active"}>
<a href="@url(repository)/search?q=@urlEncode(query)&type=code"> <a href="@url(repository)/search?q=@urlEncode(query)&type=code">
@if(fileCount != 0){ @if(fileCount != 0){
<span class="badge pull-right">@fileCount</span> <span class="badge pull-right">@fileCount</span>
} }
Code Code
</a> </a>
</li> </li>
<li@if(active=="issue"){ class="active"}> <li@if(active=="issue"){ class="active"}>
<a href="@url(repository)/search?q=@urlEncode(query)&type=issue"> <a href="@url(repository)/search?q=@urlEncode(query)&type=issue">
@if(issueCount != 0){ @if(issueCount != 0){
<span class="badge pull-right">@issueCount</span> <span class="badge pull-right">@issueCount</span>
} }
Issue Issue
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="span9"> <div class="span9">
<form action="@url(repository)/search" method="GET"> <form action="@url(repository)/search" method="GET">
<input type="text" name="q" value="@query" style="width: 80%; margin-bottom: 0px;"/> <input type="text" name="q" value="@query" style="width: 80%; margin-bottom: 0px;"/>
<input type="submit" value="Search" class="btn" style="width: 15%;"/> <input type="submit" value="Search" class="btn" style="width: 15%;"/>
<input type="hidden" name="type" value="@active"/> <input type="hidden" name="type" value="@active"/>
</form> </form>
@body @body
</div> </div>
</div> </div>
} }

View File

@@ -1,35 +1,35 @@
@(collaborators: List[String], @(collaborators: List[String],
isGroupRepository: Boolean, isGroupRepository: Boolean,
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main("Settings", Some(repository)){ @html.main("Settings", Some(repository)){
@html.menu("settings", repository){ @html.menu("settings", repository){
@menu("collaborators", repository){ @menu("collaborators", repository){
<h3>Manage Collaborators</h3> <h3>Manage Collaborators</h3>
<ul class="collaborator"> <ul class="collaborator">
@collaborators.map { collaboratorName => @collaborators.map { collaboratorName =>
<li> <li>
<a href="@url(collaboratorName)">@collaboratorName</a> <a href="@url(collaboratorName)">@collaboratorName</a>
@if(!isGroupRepository){ @if(!isGroupRepository){
<a href="@url(repository)/settings/collaborators/remove?name=@collaboratorName" class="remove">(remove)</a> <a href="@url(repository)/settings/collaborators/remove?name=@collaboratorName" class="remove">(remove)</a>
} else { } else {
@if(repository.managers.contains(collaboratorName)){ @if(repository.managers.contains(collaboratorName)){
(Manager) (Manager)
} }
} }
</li> </li>
} }
</ul> </ul>
@if(!isGroupRepository){ @if(!isGroupRepository){
<form method="POST" action="@url(repository)/settings/collaborators/add" validate="true" autocomplete="off"> <form method="POST" action="@url(repository)/settings/collaborators/add" validate="true" autocomplete="off">
<div> <div>
<span class="error" id="error-userName"></span> <span class="error" id="error-userName"></span>
</div> </div>
@helper.html.account("userName", 300) @helper.html.account("userName", 300)
<input type="submit" class="btn" value="Add"/> <input type="submit" class="btn" value="Add"/>
</form> </form>
} }
} }
} }
} }

View File

@@ -1,26 +1,26 @@
@(active: String, repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context) @(active: String, repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
<div class="row-fluid"> <div class="row-fluid">
<div class="span3"> <div class="span3">
<div class="box"> <div class="box">
<ul class="nav nav-tabs nav-stacked side-menu"> <ul class="nav nav-tabs nav-stacked side-menu">
<li@if(active=="options"){ class="active"}> <li@if(active=="options"){ class="active"}>
<a href="@url(repository)/settings/options">Options</a> <a href="@url(repository)/settings/options">Options</a>
</li> </li>
<li@if(active=="collaborators"){ class="active"}> <li@if(active=="collaborators"){ class="active"}>
<a href="@url(repository)/settings/collaborators">Collaborators</a> <a href="@url(repository)/settings/collaborators">Collaborators</a>
</li> </li>
<li@if(active=="hooks"){ class="active"}> <li@if(active=="hooks"){ class="active"}>
<a href="@url(repository)/settings/hooks">Service Hooks</a> <a href="@url(repository)/settings/hooks">Service Hooks</a>
</li> </li>
<li@if(active=="danger"){ class="active"}> <li@if(active=="danger"){ class="active"}>
<a href="@url(repository)/settings/danger">Danger Zone</a> <a href="@url(repository)/settings/danger">Danger Zone</a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="span9"> <div class="span9">
@body @body
</div> </div>
</div> </div>

View File

@@ -1,100 +1,100 @@
@(repository: service.RepositoryService.RepositoryInfo, info: Option[Any])(implicit context: app.Context) @(repository: service.RepositoryService.RepositoryInfo, info: Option[Any])(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main("Settings", Some(repository)){ @html.main("Settings", Some(repository)){
@html.menu("settings", repository){ @html.menu("settings", repository){
@menu("options", repository){ @menu("options", repository){
@helper.html.information(info) @helper.html.information(info)
<form id="form" method="post" action="@url(repository)/settings/options" validate="true"> <form id="form" method="post" action="@url(repository)/settings/options" validate="true">
<div class="box"> <div class="box">
<div class="box-header">Settings</div> <div class="box-header">Settings</div>
<div class="box-content"> <div class="box-content">
<fieldset> <fieldset>
<label for="repositoryName" class="strong">Repository Name:</label> <label for="repositoryName" class="strong">Repository Name:</label>
<input type="text" name="repositoryName" id="repositoryName" value="@repository.name"/> <input type="text" name="repositoryName" id="repositoryName" value="@repository.name"/>
<span id="error-repositoryName" class="error"></span> <span id="error-repositoryName" class="error"></span>
</fieldset> </fieldset>
<fieldset class="margin"> <fieldset class="margin">
<label for="description" class="strong">Description:</label> <label for="description" class="strong">Description:</label>
<input type="text" name="description" id="description" style="width: 600px;" value="@repository.repository.description"/> <input type="text" name="description" id="description" style="width: 600px;" value="@repository.repository.description"/>
</fieldset> </fieldset>
<fieldset class="margin"> <fieldset class="margin">
<label for="defaultBranch" class="strong">Default Branch:</label> <label for="defaultBranch" class="strong">Default Branch:</label>
<select name="defaultBranch" id="defaultBranch"@if(repository.branchList.isEmpty){ disabled}> <select name="defaultBranch" id="defaultBranch"@if(repository.branchList.isEmpty){ disabled}>
@if(repository.branchList.isEmpty){ @if(repository.branchList.isEmpty){
<option value="none" selected>No Branch</option> <option value="none" selected>No Branch</option>
} else { } else {
@repository.branchList.map { branch => @repository.branchList.map { branch =>
<option@if(branch==repository.repository.defaultBranch){ selected}>@branch</option> <option@if(branch==repository.repository.defaultBranch){ selected}>@branch</option>
} }
} }
</select> </select>
@if(repository.branchList.isEmpty){ @if(repository.branchList.isEmpty){
<input type="hidden" name="defaultBranch" value="none"/> <input type="hidden" name="defaultBranch" value="none"/>
} }
<span class="error" id="error-defaultBranch"></span> <span class="error" id="error-defaultBranch"></span>
</fieldset> </fieldset>
<fieldset class="margin"> <fieldset class="margin">
<label class="radio"> <label class="radio">
<input type="radio" name="isPrivate" value="false" <input type="radio" name="isPrivate" value="false"
@if(!repository.repository.isPrivate ){ checked } @if(!repository.repository.isPrivate ){ checked }
@if(repository.repository.parentUserName.isDefined){ disabled } @if(repository.repository.parentUserName.isDefined){ disabled }
> >
<span class="strong">Public</span><br> <span class="strong">Public</span><br>
<div> <div>
<span>All users and guests can read this repository.</span> <span>All users and guests can read this repository.</span>
</div> </div>
</label> </label>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label class="radio"> <label class="radio">
<input type="radio" name="isPrivate" value="true" <input type="radio" name="isPrivate" value="true"
@if(repository.repository.isPrivate ){ checked } @if(repository.repository.isPrivate ){ checked }
@if(repository.repository.parentUserName.isDefined){ disabled } @if(repository.repository.parentUserName.isDefined){ disabled }
> >
<span class="strong">Private</span><br> <span class="strong">Private</span><br>
<div> <div>
<span>Only collaborators can read this repository.</span> <span>Only collaborators can read this repository.</span>
</div> </div>
</label> </label>
</fieldset> </fieldset>
</div> </div>
</div> </div>
@* @*
<div class="box"> <div class="box">
<div class="box-header">Features:</div> <div class="box-header">Features:</div>
<div class="box-content"> <div class="box-content">
<dl> <dl>
<dt> <dt>
<label class="checkbox strong"> <label class="checkbox strong">
<input type="checkbox" name="wiki" id="wiki"/> Wiki <input type="checkbox" name="wiki" id="wiki"/> Wiki
</label> </label>
</dt> </dt>
<dd> <dd>
Adds lightweight Wiki system to this repository. Adds lightweight Wiki system to this repository.
This is the simplest way to provide documentation or examples. This is the simplest way to provide documentation or examples.
Only collaborators can edit Wiki pages. Only collaborators can edit Wiki pages.
</dd> </dd>
</dl> </dl>
<hr> <hr>
<dl> <dl>
<dt> <dt>
<label class="checkbox strong"> <label class="checkbox strong">
<input type="checkbox" name="issue" id="issue"/> Issue <input type="checkbox" name="issue" id="issue"/> Issue
</label> </label>
</dt> </dt>
<dd> <dd>
Adds lightweight issue tracking integrated with this repository. Adds lightweight issue tracking integrated with this repository.
All users who have signed in and can access this repository can register an issue. All users who have signed in and can access this repository can register an issue.
</dd> </dd>
</dl> </dl>
</div> </div>
</div> </div>
*@ *@
<fieldset> <fieldset>
<input type="submit" class="btn btn-success" value="Apply changes"/> <input type="submit" class="btn btn-success" value="Apply changes"/>
</fieldset> </fieldset>
</form> </form>
} }
} }
} }

View File

@@ -1,7 +1,7 @@
@()(implicit context: app.Context) @()(implicit context: app.Context)
@import context._ @import context._
@main("Sign in"){ @main("Sign in"){
<div class="signin-form"> <div class="signin-form">
@signinform(settings) @signinform(settings)
</div> </div>
} }

View File

@@ -1,40 +1,40 @@
@(pageName: Option[String], @(pageName: Option[String],
from: String, from: String,
to: String, to: String,
diffs: Seq[util.JGitUtil.DiffInfo], diffs: Seq[util.JGitUtil.DiffInfo],
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean, hasWritePermission: Boolean,
info: Option[Any])(implicit context: app.Context) info: Option[Any])(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import org.eclipse.jgit.diff.DiffEntry.ChangeType @import org.eclipse.jgit.diff.DiffEntry.ChangeType
@html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
@helper.html.information(info) @helper.html.information(info)
@html.menu("wiki", repository){ @html.menu("wiki", repository){
<ul class="nav nav-tabs fill-width pull-left"> <ul class="nav nav-tabs fill-width pull-left">
<li> <li>
<h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1> <h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1>
</li> </li>
<li class="pull-right"> <li class="pull-right">
<div class="btn-group"> <div class="btn-group">
@if(pageName.isDefined){ @if(pageName.isDefined){
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)">View Page</a> <a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)">View Page</a>
<a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_history">Back to Page History</a> <a class="btn btn-small" href="@url(repository)/wiki/@urlEncode(pageName)/_history">Back to Page History</a>
} else { } else {
<a class="btn btn-small" href="@url(repository)/wiki/_history">Back to Wiki History</a> <a class="btn btn-small" href="@url(repository)/wiki/_history">Back to Wiki History</a>
} }
</div> </div>
</li> </li>
</ul> </ul>
@helper.html.diff(diffs, repository, None, None, false) @helper.html.diff(diffs, repository, None, None, false)
@if(hasWritePermission){ @if(hasWritePermission){
<div> <div>
@if(pageName.isDefined){ @if(pageName.isDefined){
<a href="@url(repository)/wiki/@urlEncode(pageName)/_revert/@from...@to" class="btn">Revert Changes</a> <a href="@url(repository)/wiki/@urlEncode(pageName)/_revert/@from...@to" class="btn">Revert Changes</a>
} else { } else {
<a href="@url(repository)/wiki/_revert/@from...@to" class="btn">Revert Changes</a> <a href="@url(repository)/wiki/_revert/@from...@to" class="btn">Revert Changes</a>
} }
</div> </div>
} }
} }
} }

View File

@@ -1,109 +1,109 @@
$(function(){ $(function(){
// disable Ajax cache // disable Ajax cache
$.ajaxSetup({ cache: false }); $.ajaxSetup({ cache: false });
// repository url text field // repository url text field
$('#repository-url').click(function(){ $('#repository-url').click(function(){
this.select(0, this.value.length); this.select(0, this.value.length);
}); });
// activate tooltip // activate tooltip
$('img[data-toggle=tooltip]').tooltip(); $('img[data-toggle=tooltip]').tooltip();
$('a[data-toggle=tooltip]').tooltip(); $('a[data-toggle=tooltip]').tooltip();
// anchor icon for markdown // anchor icon for markdown
$('.markdown-head').mouseenter(function(e){ $('.markdown-head').mouseenter(function(e){
$(e.target).children('a.markdown-anchor-link').show(); $(e.target).children('a.markdown-anchor-link').show();
}); });
$('.markdown-head').mouseleave(function(e){ $('.markdown-head').mouseleave(function(e){
var anchorLink = $(e.target).children('a.markdown-anchor-link'); var anchorLink = $(e.target).children('a.markdown-anchor-link');
if(anchorLink.data('active') != true){ if(anchorLink.data('active') != true){
anchorLink.hide(); anchorLink.hide();
} }
}); });
$('a.markdown-anchor-link').mouseenter(function(e){ $('a.markdown-anchor-link').mouseenter(function(e){
$(e.target).data('active', true); $(e.target).data('active', true);
}); });
$('a.markdown-anchor-link').mouseleave(function(e){ $('a.markdown-anchor-link').mouseleave(function(e){
$(e.target).data('active', false); $(e.target).data('active', false);
$(e.target).hide(); $(e.target).hide();
}); });
// syntax highlighting by google-code-prettify // syntax highlighting by google-code-prettify
prettyPrint(); prettyPrint();
}); });
function displayErrors(data){ function displayErrors(data){
var i = 0; var i = 0;
$.each(data, function(key, value){ $.each(data, function(key, value){
$('#error-' + key.split(".").join("_")).text(value); $('#error-' + key.split(".").join("_")).text(value);
if(i == 0){ if(i == 0){
$('#' + key).focus(); $('#' + key).focus();
} }
i++; i++;
}); });
} }
(function($){ (function($){
$.fn.watch = function(callback){ $.fn.watch = function(callback){
var timer = null; var timer = null;
var prevValue = this.val(); var prevValue = this.val();
this.on('focus', function(e){ this.on('focus', function(e){
window.clearInterval(timer); window.clearInterval(timer);
timer = window.setInterval(function(){ timer = window.setInterval(function(){
var newValue = $(e.target).val(); var newValue = $(e.target).val();
if(prevValue != newValue){ if(prevValue != newValue){
callback(); callback();
} }
prevValue = newValue; prevValue = newValue;
}, 10); }, 10);
}); });
this.on('blur', function(){ this.on('blur', function(){
window.clearInterval(timer); window.clearInterval(timer);
}); });
}; };
})(jQuery); })(jQuery);
function diffUsingJS(oldTextId, newTextId, outputId) { function diffUsingJS(oldTextId, newTextId, outputId) {
// get the baseText and newText values from the two textboxes, and split them into lines // get the baseText and newText values from the two textboxes, and split them into lines
var oldText = document.getElementById(oldTextId).value; var oldText = document.getElementById(oldTextId).value;
if(oldText == ''){ if(oldText == ''){
var oldLines = []; var oldLines = [];
} else { } else {
var oldLines = difflib.stringAsLines(oldText); var oldLines = difflib.stringAsLines(oldText);
} }
var newText = document.getElementById(newTextId).value var newText = document.getElementById(newTextId).value
if(newText == ''){ if(newText == ''){
var newLines = []; var newLines = [];
} else { } else {
var newLines = difflib.stringAsLines(newText); var newLines = difflib.stringAsLines(newText);
} }
// create a SequenceMatcher instance that diffs the two sets of lines // create a SequenceMatcher instance that diffs the two sets of lines
var sm = new difflib.SequenceMatcher(oldLines, newLines); var sm = new difflib.SequenceMatcher(oldLines, newLines);
// get the opcodes from the SequenceMatcher instance // get the opcodes from the SequenceMatcher instance
// opcodes is a list of 3-tuples describing what changes should be made to the base text // opcodes is a list of 3-tuples describing what changes should be made to the base text
// in order to yield the new text // in order to yield the new text
var opcodes = sm.get_opcodes(); var opcodes = sm.get_opcodes();
var diffoutputdiv = document.getElementById(outputId); var diffoutputdiv = document.getElementById(outputId);
while (diffoutputdiv.firstChild) diffoutputdiv.removeChild(diffoutputdiv.firstChild); while (diffoutputdiv.firstChild) diffoutputdiv.removeChild(diffoutputdiv.firstChild);
// build the diff view and add it to the current DOM // build the diff view and add it to the current DOM
diffoutputdiv.appendChild(diffview.buildView({ diffoutputdiv.appendChild(diffview.buildView({
baseTextLines: oldLines, baseTextLines: oldLines,
newTextLines: newLines, newTextLines: newLines,
opcodes: opcodes, opcodes: opcodes,
contextSize: 4, contextSize: 4,
viewType: 1 viewType: 1
})); }));
} }
function jqSelectorEscape(val) { function jqSelectorEscape(val) {
return val.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&'); return val.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&');
} }

View File

@@ -1,40 +1,40 @@
$(function(){ $(function(){
$.each($('form[validate=true]'), function(i, form){ $.each($('form[validate=true]'), function(i, form){
$(form).submit(validate); $(form).submit(validate);
}); });
$.each($('input[formaction]'), function(i, input){ $.each($('input[formaction]'), function(i, input){
$(input).click(function(){ $(input).click(function(){
var form = $(input).parents('form') var form = $(input).parents('form')
$(form).attr('action', $(input).attr('formaction')) $(form).attr('action', $(input).attr('formaction'))
}); });
}); });
}); });
function validate(e){ function validate(e){
var form = $(e.target); var form = $(e.target);
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){
// clear all error messages // clear all error messages
$('.error').text(''); $('.error').text('');
if($.isEmptyObject(data)){ if($.isEmptyObject(data)){
form.data('validated', true); form.data('validated', true);
form.submit(); form.submit();
form.data('validated', false); form.data('validated', false);
} else { } else {
form.data('validated', false); form.data('validated', false);
displayErrors(data); displayErrors(data);
} }
}, 'json'); }, 'json');
return false; return false;
} }
function displayErrors(data){ function displayErrors(data){
$.each(data, function(key, value){ $.each(data, function(key, value){
$('#error-' + key.split(".").join("_")).text(value); $('#error-' + key.split(".").join("_")).text(value);
}); });
} }

View File

@@ -26,106 +26,108 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* ***** END LICENSE BLOCK ***** */ * ***** END LICENSE BLOCK ***** */
ace.define('ace/mode/diff', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/mode/diff_highlight_rules', 'ace/mode/folding/diff'], function(require, exports, module) { ace.define('ace/mode/diff', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/mode/diff_highlight_rules', 'ace/mode/folding/diff'], function(require, exports, module) {
var oop = require("../lib/oop"); var oop = require("../lib/oop");
var TextMode = require("./text").Mode; var TextMode = require("./text").Mode;
var HighlightRules = require("./diff_highlight_rules").DiffHighlightRules; var HighlightRules = require("./diff_highlight_rules").DiffHighlightRules;
var FoldMode = require("./folding/diff").FoldMode; var FoldMode = require("./folding/diff").FoldMode;
var Mode = function() { var Mode = function() {
this.HighlightRules = HighlightRules; this.HighlightRules = HighlightRules;
this.foldingRules = new FoldMode(["diff", "index", "\\+{3}", "@@|\\*{5}"], "i"); this.foldingRules = new FoldMode(["diff", "index", "\\+{3}", "@@|\\*{5}"], "i");
}; };
oop.inherits(Mode, TextMode); oop.inherits(Mode, TextMode);
(function() { (function() {
this.$id = "ace/mode/diff"; this.$id = "ace/mode/diff";
}).call(Mode.prototype); }).call(Mode.prototype);
exports.Mode = Mode; exports.Mode = Mode;
}); });
ace.define('ace/mode/diff_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text_highlight_rules'], function(require, exports, module) { ace.define('ace/mode/diff_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text_highlight_rules'], function(require, exports, module) {
var oop = require("../lib/oop"); var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var DiffHighlightRules = function() { var DiffHighlightRules = function() {
this.$rules = { this.$rules = {
"start" : [{ "start" : [{
regex: "^(?:\\*{15}|={67}|-{3}|\\+{3})$", regex: "^(?:\\*{15}|={67}|-{3}|\\+{3})$",
token: "punctuation.definition.separator.diff", token: "punctuation.definition.separator.diff",
"name": "keyword" "name": "keyword"
}, { //diff.range.unified }, { //diff.range.unified
regex: "^(@@)(\\s*.+?\\s*)(@@)(.*)$", regex: "^(@@)(\\s*.+?\\s*)(@@)(.*)$",
token: [ token: [
"constant", "constant",
"constant.numeric", "constant.numeric",
"constant", "constant",
"comment.doc.tag" "comment.doc.tag"
] ]
}, { //diff.range.normal }, { //diff.range.normal
regex: "^(\\d+)([,\\d]+)(a|d|c)(\\d+)([,\\d]+)(.*)$", regex: "^(\\d+)([,\\d]+)(a|d|c)(\\d+)([,\\d]+)(.*)$",
token: [ token: [
"constant.numeric", "constant.numeric",
"punctuation.definition.range.diff", "punctuation.definition.range.diff",
"constant.function", "constant.function",
"constant.numeric", "constant.numeric",
"punctuation.definition.range.diff", "punctuation.definition.range.diff",
"invalid" "invalid"
], ],
"name": "meta."
}, {
regex: "^(\\-{3}|\\+{3}|\\*{3})( .+)$", regex: "^(\\-{3}|\\+{3}|\\*{3})( .+)$",
token: [ token: [
regex: "^(\\-{3}|\\+{3}|\\*{3})( .+)$", "constant.numeric",
token: [ "meta.tag"
"constant.numeric", ]
"meta.tag" }, { // added
] regex: "^([!+>])(.*?)(\\s*)$",
}, { // added token: [
regex: "^([!+>])(.*?)(\\s*)$", "support.constant",
token: [ "text",
"support.constant", "invalid"
"text", ]
"invalid" }, { // removed
] regex: "^([<\\-])(.*?)(\\s*)$",
}, { // removed token: [
regex: "^([<\\-])(.*?)(\\s*)$", "support.function",
token: [ "string",
"support.function", "invalid"
"string", ]
"invalid" }, {
] regex: "^(diff)(\\s+--\\w+)?(.+?)( .+)?$",
}, { token: ["variable", "variable", "keyword", "variable"]
regex: "^(diff)(\\s+--\\w+)?(.+?)( .+)?$", }, {
token: ["variable", "variable", "keyword", "variable"] regex: "^Index.+$",
}, { token: "variable"
regex: "^Index.+$", }, {
regex: "^\\s+$", regex: "^\\s+$",
token: "text" token: "text"
regex: "^\\s+$", }, {
regex: "\\s*$", regex: "\\s*$",
token: "invalid" token: "invalid"
}, { }, {
defaultToken: "invisible", defaultToken: "invisible",
caseInsensitive: true caseInsensitive: true
defaultToken: "invisible", }
caseInsensitive: true ]
} };
] };
};
}; oop.inherits(DiffHighlightRules, TextHighlightRules);
oop.inherits(DiffHighlightRules, TextHighlightRules); exports.DiffHighlightRules = DiffHighlightRules;
}); });
ace.define('ace/mode/folding/diff', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/folding/fold_mode', 'ace/range'], function(require, exports, module) { ace.define('ace/mode/folding/diff', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/folding/fold_mode', 'ace/range'], function(require, exports, module) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long