mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 14:35:52 +01:00
(refs #310)Convert all CRLF to LF
This commit is contained in:
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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." }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
@@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = {}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"> </span>
|
<span style="background-color: #@label.color;" class="label-color"> </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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, '\\$&');
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
180
src/main/webapp/assets/vendors/ace/mode-diff.js
vendored
180
src/main/webapp/assets/vendors/ace/mode-diff.js
vendored
@@ -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) {
|
||||||
|
|||||||
6024
src/main/webapp/assets/vendors/ace/mode-jsoniq.js
vendored
6024
src/main/webapp/assets/vendors/ace/mode-jsoniq.js
vendored
File diff suppressed because one or more lines are too long
5988
src/main/webapp/assets/vendors/ace/mode-xquery.js
vendored
5988
src/main/webapp/assets/vendors/ace/mode-xquery.js
vendored
File diff suppressed because one or more lines are too long
8618
src/main/webapp/assets/vendors/ace/worker-html.js
vendored
8618
src/main/webapp/assets/vendors/ace/worker-html.js
vendored
File diff suppressed because it is too large
Load Diff
122808
src/main/webapp/assets/vendors/ace/worker-xquery.js
vendored
122808
src/main/webapp/assets/vendors/ace/worker-xquery.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user