Merge branch 'master' into toggle_gravatar

Conflicts:
	src/main/scala/view/AvatarImageProvider.scala
This commit is contained in:
takezoe
2013-08-06 01:58:47 +09:00
59 changed files with 2956 additions and 748 deletions

View File

@@ -102,7 +102,28 @@ trait ActivityService {
s"[user:${activityUserName}] created branch [tag:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate)
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String) =
Activities.autoInc insert(userName, repositoryName, activityUserName,
"fork",
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${activityUserName}/${repositoryName}]",
None,
currentDate)
def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
Activities.autoInc insert(userName, repositoryName, activityUserName,
"open_pullreq",
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title),
currentDate)
def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String): Unit =
Activities.autoInc insert(userName, repositoryName, activityUserName,
"merge_pullreq",
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(message),
currentDate)
def insertCommitId(userName: String, repositoryName: String, commitId: String) = {
CommitLog insert (userName, repositoryName, commitId)
}

View File

@@ -42,18 +42,18 @@ trait IssuesService {
/**
* Returns the count of the search result against issues.
*
* @param owner the repository owner
* @param repository the repository name
* @param condition the search condition
* @param filter the filter type ("all", "assigned" or "created_by")
* @param userName the filter user name required for "assigned" and "created_by"
* @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 repos Tuple of the repository owner and the repository name
* @return the count of the search result
*/
def countIssue(owner: String, repository: String, condition: IssueSearchCondition, filter: String, userName: Option[String]): Int = {
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
repos: (String, String)*): Int = {
// TODO It must be _.length instead of map (_.issueId) list).length.
// But it does not work on Slick 1.0.1 (worked on Slick 1.0.0).
// https://github.com/slick/slick/issues/170
(searchIssueQuery(owner, repository, condition, filter, userName) map (_.issueId) list).length
(searchIssueQuery(repos, condition, filterUser, onlyPullRequest) map (_.issueId) list).length
}
/**
* Returns the Map which contains issue count for each labels.
@@ -61,14 +61,13 @@ trait IssuesService {
* @param owner the repository owner
* @param repository the repository name
* @param condition the search condition
* @param filter the filter type ("all", "assigned" or "created_by")
* @param userName the filter user name required for "assigned" and "created_by"
* @return the Map which contains issue count for each labels (key is label name, value is issue count),
* @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)
*/
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
filter: String, userName: Option[String]): Map[String, Int] = {
filterUser: Map[String, String]): Map[String, Int] = {
searchIssueQuery(owner, repository, condition.copy(labels = Set.empty), filter, userName)
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
.innerJoin(IssueLabels).on { (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
}
@@ -83,76 +82,97 @@ trait IssuesService {
}
.toMap
}
/**
* Returns list which contains issue count for each repository.
* If the issue does not exist, its repository is not included in the result.
*
* @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
* @param repos Tuple of the repository owner and the repository name
* @return list which contains issue count for each repository
*/
def countIssueGroupByRepository(
condition: IssueSearchCondition, filterUser: Map[String, String], repos: (String, String)*): List[(String, String, Int)] = {
searchIssueQuery(repos, condition.copy(repo = None), filterUser, false)
.groupBy { t =>
t.userName ~ t.repositoryName
}
.map { case (repo, t) =>
repo ~ t.length
}
.filter (_._3 > 0.bind)
.list
}
/**
* Returns the search result against issues.
*
* @param owner the repository owner
* @param repository the repository name
* @param condition the search condition
* @param filter the filter type ("all", "assigned" or "created_by")
* @param userName the filter user name required for "assigned" and "created_by"
* @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 offset the offset for pagination
* @param limit the limit for pagination
* @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)
*/
def searchIssue(owner: String, repository: String, condition: IssueSearchCondition,
filter: String, userName: Option[String], offset: Int, limit: Int): List[(Issue, List[Label], Int)] = {
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = {
// get issues and comment count
val issues = searchIssueQuery(owner, repository, condition, filter, userName)
.leftJoin(Query(IssueComments)
.filter { t =>
(t.byRepository(owner, repository)) &&
(t.action inSetBind Seq("comment", "close_comment", "reopen_comment"))
// get issues and comment count and labels
searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.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) }
.map { case (((t1, t2), t3), t4) =>
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
}
.groupBy { _.issueId }
.map { case (issueId, t) => issueId ~ t.length }).on((t1, t2) => t1.issueId is t2._1)
.sortBy { case (t1, t2) =>
(condition.sort match {
case "created" => t1.registeredDate
case "comments" => t2._2
case "updated" => t1.updatedDate
}) match {
case sort => condition.direction match {
case "asc" => sort asc
case "desc" => sort desc
.sortBy(_._4) // labelName
.sortBy { case (t1, commentCount, _,_,_) =>
(condition.sort match {
case "created" => t1.registeredDate
case "comments" => commentCount
case "updated" => t1.updatedDate
}) match {
case sort => condition.direction match {
case "asc" => sort asc
case "desc" => sort desc
}
}
}
}
.map { case (t1, t2) => (t1, t2._2.ifNull(0)) }
.drop(offset).take(limit)
.list
// get labels
val labels = Query(IssueLabels)
.innerJoin(Labels).on { (t1, t2) =>
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
}
.filter { case (t1, t2) =>
(t1.byRepository(owner, repository)) &&
(t1.issueId inSetBind (issues.map(_._1.issueId)))
}
.sortBy { case (t1, t2) => t1.issueId ~ t2.labelName }
.map { case (t1, t2) => (t1.issueId, t2) }
.list
issues.map { case (issue, commentCount) =>
(issue, labels.collect { case (issueId, labels) if(issueId == issue.issueId) => labels }, commentCount)
}
.drop(offset).take(limit)
.list
.splitWith { (c1, c2) =>
c1._1.userName == c2._1.userName &&
c1._1.repositoryName == c2._1.repositoryName &&
c1._1.issueId == c2._1.issueId
}
.map { issues => issues.head match {
case (issue, commentCount, _,_,_) =>
(issue,
issues.flatMap { t => t._3.map (
Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
)} toList,
commentCount)
}} toList
}
/**
* Assembles query for conditional issue searching.
*/
private def searchIssueQuery(owner: String, repository: String, condition: IssueSearchCondition, filter: String, userName: Option[String]) =
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
filterUser: Map[String, String], onlyPullRequest: Boolean) =
Query(Issues) filter { t1 =>
(t1.byRepository(owner, repository)) &&
condition.repo
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
.getOrElse (repos)
.map { case (owner, repository) => t1.byRepository(owner, repository) }
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
(t1.closed is (condition.state == "closed").bind) &&
(t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
(t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
(t1.assignedUserName is userName.get.bind, filter == "assigned") &&
(t1.openedUserName is userName.get.bind, filter == "created_by") &&
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
(t1.pullRequest is true.bind, onlyPullRequest) &&
(IssueLabels filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
(t2.labelId in
@@ -164,7 +184,7 @@ trait IssuesService {
}
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int]) =
assignedUserName: Option[String], milestoneId: Option[Int], isPullRequest: Boolean = false) =
// next id number
sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
.firstOption.filter { id =>
@@ -179,7 +199,8 @@ trait IssuesService {
content,
false,
currentDate,
currentDate)
currentDate,
isPullRequest)
// increment issue id
IssueId
@@ -250,39 +271,44 @@ trait IssuesService {
val keywords = splitWords(query.toLowerCase)
// Search Issue
val issues = Query(Issues).filter { t =>
keywords.map { keyword =>
(t.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
(t.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
} .reduceLeft(_ && _)
}.map { t => (t, 0, t.content.?) }
val issues = Issues
.innerJoin(IssueOutline).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
}
.filter { case (t1, t2) =>
keywords.map { keyword =>
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
} .reduceLeft(_ && _)
}
.map { case (t1, t2) =>
(t1, 0, t1.content.?, t2.commentCount)
}
// Search IssueComment
val comments = Query(IssueComments).innerJoin(Issues).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
}.filter { case (t1, t2) =>
keywords.map { query =>
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
}.reduceLeft(_ && _)
}.map { case (t1, t2) => (t2, t1.commentId, t1.content.?) }
val comments = IssueComments
.innerJoin(Issues).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
}
.innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
}
.filter { case ((t1, t2), t3) =>
keywords.map { query =>
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
}.reduceLeft(_ && _)
}
.map { case ((t1, t2), t3) =>
(t2, t1.commentId, t1.content.?, t3.commentCount)
}
def getCommentCount(issue: Issue): Int = {
Query(IssueComments)
.filter { t =>
t.byIssue(issue.userName, issue.repositoryName, issue.issueId) &&
(t.action inSetBind Seq("comment", "close_comment", "reopen_comment"))
}
.map(_.issueId)
.list.length
}
issues.union(comments).sortBy { case (issue, commentId, _) =>
issues.union(comments).sortBy { case (issue, commentId, _, _) =>
issue.issueId ~ commentId
}.list.splitWith { case ((issue1, _, _), (issue2, _, _)) =>
}.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
issue1.issueId == issue2.issueId
}.map { result =>
val (issue, _, content) = result.head
(issue, getCommentCount(issue) , content.getOrElse(""))
}.map { _.head match {
case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
}
}.toList
}
@@ -333,6 +359,13 @@ object IssuesService {
param(request, "state", Seq("open", "closed")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
def page(request: HttpServletRequest) = try {
val i = param(request, "page").getOrElse("1").toInt
if(i <= 0) 1 else i
} catch {
case e: NumberFormatException => 1
}
}
}

View File

@@ -0,0 +1,57 @@
package service
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
import model._
trait PullRequestService { self: IssuesService =>
import PullRequestService._
def getPullRequest(owner: String, repository: String, issueId: Int): Option[(Issue, PullRequest)] = {
val issue = getIssue(owner, repository, issueId.toString)
if(issue.isDefined){
Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).firstOption match {
case Some(pullreq) => Some((issue.get, pullreq))
case None => None
}
} else None
}
def getPullRequestCount(closed: Boolean, repository: Option[(String, String)]): List[PullRequestCount] =
Query(PullRequests)
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
.filter { case (t1, t2) =>
(t2.closed is closed.bind) &&
(t1.userName is repository.get._1, repository.isDefined) &&
(t1.repositoryName is repository.get._2, repository.isDefined)
}
.groupBy { case (t1, t2) => t2.openedUserName }
.map { case (userName, t) => userName ~ t.length }
.list
.map { x => PullRequestCount(x._1, x._2) }
.sortBy(_.count).reverse
def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int,
originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String,
commitIdFrom: String, commitIdTo: String): Unit =
PullRequests insert (PullRequest(
originUserName,
originRepositoryName,
issueId,
originBranch,
requestUserName,
requestRepositoryName,
requestBranch,
commitIdFrom,
commitIdTo))
}
object PullRequestService {
val PullRequestLimit = 25
case class PullRequestCount(userName: String, count: Int)
}

View File

@@ -15,19 +15,27 @@ trait RepositoryService { self: AccountService =>
* @param userName the user name of the repository owner
* @param description the repository description
* @param isPrivate the repository type (private is true, otherwise false)
* @param originRepositoryName specify for the forked repository. (default is None)
* @param originUserName specify for the forked repository. (default is None)
*/
def createRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean): Unit = {
def createRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean,
originRepositoryName: Option[String] = None, originUserName: Option[String] = None,
parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None): Unit = {
Repositories insert
Repository(
userName = userName,
repositoryName = repositoryName,
isPrivate = isPrivate,
description = description,
defaultBranch = "master",
registeredDate = currentDate,
updatedDate = currentDate,
lastActivityDate = currentDate)
userName = userName,
repositoryName = repositoryName,
isPrivate = isPrivate,
description = description,
defaultBranch = "master",
registeredDate = currentDate,
updatedDate = currentDate,
lastActivityDate = currentDate,
originUserName = originUserName,
originRepositoryName = originRepositoryName,
parentUserName = parentUserName,
parentRepositoryName = parentRepositoryName)
IssueId insert (userName, repositoryName, 0)
}
@@ -53,39 +61,6 @@ trait RepositoryService { self: AccountService =>
def getRepositoryNamesOfUser(userName: String): List[String] =
Query(Repositories) filter(_.userName is userName.bind) map (_.repositoryName) list
/**
* Returns the list of specified user's repositories information.
*
* @param userName the user name
* @param baseUrl the base url of this application
* @param loginUserName the logged in user name
* @return the list of repository information which is sorted in descending order of lastActivityDate.
*/
def getVisibleRepositories(userName: String, baseUrl: String, loginUserName: Option[String]): List[RepositoryInfo] = {
val q1 = Repositories
.filter { t => t.userName is userName.bind }
.map { r => r }
val q2 = Collaborators
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
.filter{ case (t1, t2) => t1.collaboratorName is userName.bind}
.map { case (t1, t2) => t2 }
def visibleFor(t: Repositories.type, loginUserName: Option[String]) = {
loginUserName match {
case Some(x) => (t.isPrivate is false.bind) || (
(t.isPrivate is true.bind) && ((t.userName is x.bind) || (Collaborators.filter { c =>
c.byRepository(t.userName, t.repositoryName) && (c.collaboratorName is x.bind)
}.exists)))
case None => (t.isPrivate is false.bind)
}
}
q1.union(q2).filter(visibleFor(_, loginUserName)).sortBy(_.lastActivityDate desc).list map { repository =>
new RepositoryInfo(JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), repository)
}
}
/**
* Returns the specified repository information.
*
@@ -96,34 +71,62 @@ trait RepositoryService { self: AccountService =>
*/
def getRepository(userName: String, repositoryName: String, baseUrl: String): Option[RepositoryInfo] = {
(Query(Repositories) filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
new RepositoryInfo(JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), repository)
new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository,
getForkedCount(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
))
}
}
def getUserRepositories(userName: String, baseUrl: String): List[RepositoryInfo] = {
Query(Repositories).filter { t1 =>
(t1.userName is userName.bind) ||
(Query(Collaborators).filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName is userName.bind)} exists)
}.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository,
getForkedCount(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
))
}
}
/**
* Returns the list of accessible repositories information for the specified account user.
*
* @param account the account
* Returns the list of visible repositories for the specified user.
* If repositoryUserName is given then filters results by repository owner.
*
* @param loginAccount the logged in account
* @param baseUrl the base url of this application
* @return the repository informations which is sorted in descending order of lastActivityDate.
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
* @return the repository information which is sorted in descending order of lastActivityDate.
*/
def getAccessibleRepositories(account: Option[Account], baseUrl: String): List[RepositoryInfo] = {
def newRepositoryInfo(repository: Repository): RepositoryInfo = {
new RepositoryInfo(JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), repository)
}
(account match {
def getVisibleRepositories(loginAccount: Option[Account], baseUrl: String, repositoryUserName: Option[String] = None): List[RepositoryInfo] = {
(loginAccount match {
// for Administrators
case Some(x) if(x.isAdmin) => Query(Repositories)
// for Normal Users
case Some(x) if(!x.isAdmin) =>
Query(Repositories) filter { t => (t.isPrivate is false.bind) ||
(Query(Collaborators).filter(t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName is x.userName.bind)) exists)
(Query(Collaborators).filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName is x.userName.bind)} exists)
}
// for Guests
case None => Query(Repositories) filter(_.isPrivate is false.bind)
}).sortBy(_.lastActivityDate desc).list.map(newRepositoryInfo _)
}).filter { t =>
repositoryUserName.map { userName => t.userName is userName.bind } getOrElse ConstColumn.TRUE
}.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository,
getForkedCount(
repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName)
))
}
}
/**
@@ -189,17 +192,39 @@ trait RepositoryService { self: AccountService =>
}
}
// TODO It must be _.length instead of map (_.issueId) list).length.
// But it does not work on Slick 1.0.1 (worked on Slick 1.0.0).
// https://github.com/slick/slick/issues/170
private def getForkedCount(userName: String, repositoryName: String): Int =
Query(Repositories).filter { t =>
(t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind)
}.list.length
def getForkedRepositoryTree(userName: String, repositoryName: String): RepositoryTreeNode = {
RepositoryTreeNode(userName, repositoryName,
Query(Repositories).filter { t =>
(t.parentUserName is userName.bind) && (t.parentRepositoryName is repositoryName.bind)
}.map { t =>
t.userName ~ t.repositoryName
}.list.map { case (userName, repositoryName) =>
getForkedRepositoryTree(userName, repositoryName)
}
)
}
}
object RepositoryService {
case class RepositoryInfo(owner: String, name: String, url: String, repository: Repository,
commitCount: Int, branchList: List[String], tags: List[util.JGitUtil.TagInfo]){
commitCount: Int, forkedCount: Int, branchList: List[String], tags: List[util.JGitUtil.TagInfo]){
def this(repo: JGitUtil.RepositoryInfo, model: Repository) = {
this(repo.owner, repo.name, repo.url, model, repo.commitCount, repo.branchList, repo.tags)
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int) = {
this(repo.owner, repo.name, repo.url, model, repo.commitCount, forkedCount, repo.branchList, repo.tags)
}
}
case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode])
}

View File

@@ -4,10 +4,7 @@ import java.io.File
import java.util.Date
import org.eclipse.jgit.api.Git
import org.apache.commons.io.FileUtils
import util.JGitUtil.DiffInfo
import util.{Directory, JGitUtil}
import org.eclipse.jgit.treewalk.CanonicalTreeParser
import java.util.concurrent.ConcurrentHashMap
import util.{Directory, JGitUtil, LockUtil}
object WikiService {
@@ -31,40 +28,13 @@ object WikiService {
*/
case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
/**
* lock objects
*/
private val locks = new ConcurrentHashMap[String, AnyRef]()
/**
* Returns the lock object for the specified repository.
*/
private def getLockObject(owner: String, repository: String): AnyRef = synchronized {
val key = owner + "/" + repository
if(!locks.containsKey(key)){
locks.put(key, new AnyRef())
}
locks.get(key)
}
/**
* Synchronizes a given function which modifies the working copy of the wiki repository.
*
* @param owner the repository owner
* @param repository the repository name
* @param f the function which modifies the working copy of the wiki repository
* @tparam T the return type of the given function
* @return the result of the given function
*/
def lock[T](owner: String, repository: String)(f: => T): T = getLockObject(owner, repository).synchronized(f)
}
trait WikiService {
import WikiService._
def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit = {
lock(owner, repository){
LockUtil.lock(s"${owner}/${repository}/wiki"){
val dir = Directory.getWikiRepositoryDir(owner, repository)
if(!dir.exists){
try {
@@ -126,7 +96,7 @@ trait WikiService {
def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
content: String, committer: model.Account, message: String): Unit = {
lock(owner, repository){
LockUtil.lock(s"${owner}/${repository}/wiki"){
// clone working copy
val workDir = Directory.getWikiWorkDir(owner, repository)
cloneOrPullWorkingCopy(workDir, owner, repository)
@@ -162,8 +132,9 @@ trait WikiService {
/**
* Delete the wiki page.
*/
def deleteWikiPage(owner: String, repository: String, pageName: String, committer: String, message: String): Unit = {
lock(owner, repository){
def deleteWikiPage(owner: String, repository: String, pageName: String,
committer: String, mailAddress: String, message: String): Unit = {
LockUtil.lock(s"${owner}/${repository}/wiki"){
// clone working copy
val workDir = Directory.getWikiWorkDir(owner, repository)
cloneOrPullWorkingCopy(workDir, owner, repository)
@@ -175,34 +146,12 @@ trait WikiService {
git.rm.addFilepattern(pageName + ".md").call
// commit and push
// TODO committer's mail address
git.commit.setAuthor(committer, committer + "@devnull").setMessage(message).call
git.commit.setAuthor(committer, mailAddress).setMessage(message).call
git.push.call
}
}
}
/**
* Returns differences between specified commits.
*/
def getWikiDiffs(git: Git, commitId1: String, commitId2: String): List[DiffInfo] = {
// get diff between specified commit and its previous commit
val reader = git.getRepository.newObjectReader
val oldTreeIter = new CanonicalTreeParser
oldTreeIter.reset(reader, git.getRepository.resolve(commitId1 + "^{tree}"))
val newTreeIter = new CanonicalTreeParser
newTreeIter.reset(reader, git.getRepository.resolve(commitId2 + "^{tree}"))
import scala.collection.JavaConverters._
git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff =>
DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath,
JGitUtil.getContent(git, diff.getOldId.toObjectId, false).map(new String(_, "UTF-8")),
JGitUtil.getContent(git, diff.getNewId.toObjectId, false).map(new String(_, "UTF-8")))
}.toList
}
private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = {
if(!workDir.exists){
val git =