diff --git a/src/main/scala/app/CreateRepositoryController.scala b/src/main/scala/app/CreateRepositoryController.scala index 892ad19..deb5493 100644 --- a/src/main/scala/app/CreateRepositoryController.scala +++ b/src/main/scala/app/CreateRepositoryController.scala @@ -1,7 +1,7 @@ package app import util.Directory._ -import util.{JGitUtil, UsersAuthenticator, ReferrerAuthenticator} +import util.{LockUtil, JGitUtil, UsersAuthenticator, ReferrerAuthenticator} import service._ import java.io.File import org.eclipse.jgit.api.Git @@ -48,119 +48,125 @@ * Create new repository. */ post("/new", newForm)(usersOnly { form => - val ownerAccount = getAccountByUserName(form.owner).get - val loginAccount = context.loginAccount.get - val loginUserName = loginAccount.userName + LockUtil.lock(s"${form.owner}/${form.name}/create"){ + if(getRepository(form.owner, form.name, baseUrl).isEmpty){ + val ownerAccount = getAccountByUserName(form.owner).get + val loginAccount = context.loginAccount.get + val loginUserName = loginAccount.userName - // Insert to the database at first - createRepository(form.name, form.owner, form.description, form.isPrivate) + // Insert to the database at first + createRepository(form.name, form.owner, form.description, form.isPrivate) - // Add collaborators for group repository - if(ownerAccount.isGroupAccount){ - getGroupMembers(form.owner).foreach { userName => - addCollaborator(form.owner, form.name, userName) + // Add collaborators for group repository + if(ownerAccount.isGroupAccount){ + getGroupMembers(form.owner).foreach { userName => + addCollaborator(form.owner, form.name, userName) + } + } + + // Insert default labels + insertDefaultLabels(loginUserName, form.name) + + // Create the actual repository + val gitdir = getRepositoryDir(form.owner, form.name) + JGitUtil.initRepository(gitdir) + + if(form.createReadme){ + val tmpdir = getInitRepositoryDir(form.owner, form.name) + try { + // Clone the repository + Git.cloneRepository.setURI(gitdir.toURI.toString).setDirectory(tmpdir).call + + // Create README.md + FileUtils.writeStringToFile(new File(tmpdir, "README.md"), + if(form.description.nonEmpty){ + form.name + "\n" + + "===============\n" + + "\n" + + form.description.get + } else { + form.name + "\n" + + "===============\n" + }, "UTF-8") + + val git = Git.open(tmpdir) + git.add.addFilepattern("README.md").call + git.commit + .setCommitter(new PersonIdent(loginUserName, loginAccount.mailAddress)) + .setMessage("Initial commit").call + git.push.call + + } finally { + FileUtils.deleteDirectory(tmpdir) + } + } + + // Create Wiki repository + createWikiRepository(loginAccount, form.owner, form.name) + + // Record activity + recordCreateRepositoryActivity(form.owner, form.name, loginUserName) } + + // redirect to the repository + redirect(s"/${form.owner}/${form.name}") } - - // Insert default labels - insertDefaultLabels(loginUserName, form.name) - - // Create the actual repository - val gitdir = getRepositoryDir(form.owner, form.name) - JGitUtil.initRepository(gitdir) - - if(form.createReadme){ - val tmpdir = getInitRepositoryDir(form.owner, form.name) - try { - // Clone the repository - Git.cloneRepository.setURI(gitdir.toURI.toString).setDirectory(tmpdir).call - - // Create README.md - FileUtils.writeStringToFile(new File(tmpdir, "README.md"), - if(form.description.nonEmpty){ - form.name + "\n" + - "===============\n" + - "\n" + - form.description.get - } else { - form.name + "\n" + - "===============\n" - }, "UTF-8") - - val git = Git.open(tmpdir) - git.add.addFilepattern("README.md").call - git.commit - .setCommitter(new PersonIdent(loginUserName, loginAccount.mailAddress)) - .setMessage("Initial commit").call - git.push.call - - } finally { - FileUtils.deleteDirectory(tmpdir) - } - } - - // Create Wiki repository - createWikiRepository(loginAccount, form.owner, form.name) - - // Record activity - recordCreateRepositoryActivity(form.owner, form.name, loginUserName) - - // redirect to the repository - redirect(s"/${form.owner}/${form.name}") }) post("/:owner/:repository/_fork")(referrersOnly { repository => val loginAccount = context.loginAccount.get val loginUserName = loginAccount.userName - if(getRepository(loginUserName, repository.name, baseUrl).isEmpty){ - // Insert to the database at first - val originUserName = repository.repository.originUserName.getOrElse(repository.owner) - val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) + LockUtil.lock(s"${loginUserName}/${repository.name}/create"){ + if(getRepository(loginUserName, repository.name, baseUrl).isEmpty){ + // Insert to the database at first + val originUserName = repository.repository.originUserName.getOrElse(repository.owner) + val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) - createRepository( - repositoryName = repository.name, - userName = loginUserName, - description = repository.repository.description, - isPrivate = repository.repository.isPrivate, - originRepositoryName = Some(originRepositoryName), - originUserName = Some(originUserName), - parentRepositoryName = Some(repository.name), - parentUserName = Some(repository.owner) - ) + createRepository( + repositoryName = repository.name, + userName = loginUserName, + description = repository.repository.description, + isPrivate = repository.repository.isPrivate, + originRepositoryName = Some(originRepositoryName), + originUserName = Some(originUserName), + parentRepositoryName = Some(repository.name), + parentUserName = Some(repository.owner) + ) - // Insert default labels - insertDefaultLabels(loginUserName, repository.name) + // Insert default labels + insertDefaultLabels(loginUserName, repository.name) - // clone repository actually - JGitUtil.cloneRepository( - getRepositoryDir(repository.owner, repository.name), - getRepositoryDir(loginUserName, repository.name)) + // clone repository actually + JGitUtil.cloneRepository( + getRepositoryDir(repository.owner, repository.name), + getRepositoryDir(loginUserName, repository.name)) - // Create Wiki repository - JGitUtil.cloneRepository( - getWikiRepositoryDir(repository.owner, repository.name), - getWikiRepositoryDir(loginUserName, repository.name)) + // Create Wiki repository + JGitUtil.cloneRepository( + getWikiRepositoryDir(repository.owner, repository.name), + getWikiRepositoryDir(loginUserName, repository.name)) - // insert commit id - JGitUtil.withGit(getRepositoryDir(loginUserName, repository.name)){ git => - JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch => - JGitUtil.getCommitLog(git, branch) match { - case Right((commits, _)) => commits.foreach { commit => - if(!existsCommitId(loginUserName, repository.name, commit.id)){ - insertCommitId(loginUserName, repository.name, commit.id) + // insert commit id + JGitUtil.withGit(getRepositoryDir(loginUserName, repository.name)){ git => + JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch => + JGitUtil.getCommitLog(git, branch) match { + case Right((commits, _)) => commits.foreach { commit => + if(!existsCommitId(loginUserName, repository.name, commit.id)){ + insertCommitId(loginUserName, repository.name, commit.id) + } } + case Left(_) => ??? } - case Left(_) => ??? } } - } - // Record activity - recordForkActivity(repository.owner, repository.name, loginUserName) + // Record activity + recordForkActivity(repository.owner, repository.name, loginUserName) + } + // redirect to the repository + redirect("/%s/%s".format(loginUserName, repository.name)) } - // redirect to the repository - redirect("/%s/%s".format(loginUserName, repository.name)) }) private def insertDefaultLabels(userName: String, repositoryName: String): Unit = { diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index 91cf46f..6e8df91 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -1,6 +1,6 @@ package app -import util.{CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator} +import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator} import util.Directory._ import util.Implicits._ import util.JGitUtil.{DiffInfo, CommitInfo} @@ -82,77 +82,85 @@ }) post("/:owner/:repository/pulls/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) => - val issueId = params("id").toInt + LockUtil.lock(s"${repository.owner}/${repository.name}/merge"){ + val issueId = params("id").toInt - getPullRequest(repository.owner, repository.name, issueId).map { case (issue, pullreq) => - val remote = getRepositoryDir(repository.owner, repository.name) - val tmpdir = new java.io.File(getTemporaryDir(repository.owner, repository.name), s"merge-${issueId}") + getPullRequest(repository.owner, repository.name, issueId).map { case (issue, pullreq) => + val remote = getRepositoryDir(repository.owner, repository.name) + val tmpdir = new java.io.File(getTemporaryDir(repository.owner, repository.name), s"merge-${issueId}") + val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).call + + try { + // TODO mark issue as 'merged' + val loginAccount = context.loginAccount.get + createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Closed", "close") + updateClosed(repository.owner, repository.name, issueId, true) + recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, form.message) + + git.checkout.setName(pullreq.branch).call + + git.fetch + .setRemote(getRepositoryDir(repository.owner, repository.name).toURI.toString) + .setRefSpecs(new RefSpec(s"refs/pull/${issueId}/head:refs/heads/${pullreq.branch}")).call + + val result = git.merge + .include(git.getRepository.resolve("FETCH_HEAD")) + .setCommit(false).call + + if(result.getConflicts != null){ + throw new RuntimeException("This pull request can't merge automatically.") + } + + // TODO merge commit + // git.commit + // .setCommitter(new PersonIdent(loginAccount.userName, loginAccount.mailAddress)) + // .setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n" + // + form.message).call + git.push.call + + val (commits, _) = getRequestCompareInfo(repository.owner, repository.name, pullreq.commitIdFrom, + pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo) + + commits.flatten.foreach { commit => + insertCommitId(repository.owner, repository.name, commit.id) + } + + redirect(s"/${repository.owner}/${repository.name}/pulls/${issueId}") + + } finally { + git.getRepository.close + FileUtils.deleteDirectory(tmpdir) + } + } getOrElse NotFound + } + }) + + private def checkConflict(userName: String, repositoryName: String, branch: String, + requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = { + LockUtil.lock(s"${userName}/${repositoryName}/merge-check"){ + val remote = getRepositoryDir(userName, repositoryName) + val tmpdir = new java.io.File(getTemporaryDir(userName, repositoryName), "merge-check") val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).call - try { - // TODO mark issue as 'merged' - val loginAccount = context.loginAccount.get - createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Closed", "close") - updateClosed(repository.owner, repository.name, issueId, true) - recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, form.message) - - git.checkout.setName(pullreq.branch).call + if(tmpdir.exists()){ + FileUtils.deleteDirectory(tmpdir) + } + git.checkout.setName(branch).call git.fetch - .setRemote(getRepositoryDir(repository.owner, repository.name).toURI.toString) - .setRefSpecs(new RefSpec(s"refs/pull/${issueId}/head:refs/heads/${pullreq.branch}")).call + .setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString) + .setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/heads/${requestBranch}")).call val result = git.merge .include(git.getRepository.resolve("FETCH_HEAD")) .setCommit(false).call - if(result.getConflicts != null){ - throw new RuntimeException("This pull request can't merge automatically.") - } - -// git.commit -// .setCommitter(new PersonIdent(loginAccount.userName, loginAccount.mailAddress)) -// .setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n" -// + form.message).call - git.push.call - - val (commits, _) = getRequestCompareInfo(repository.owner, repository.name, pullreq.commitIdFrom, - pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo) - - commits.flatten.foreach { commit => - insertCommitId(repository.owner, repository.name, commit.id) - } - - redirect(s"/${repository.owner}/${repository.name}/pulls/${issueId}") + result.getConflicts != null } finally { git.getRepository.close FileUtils.deleteDirectory(tmpdir) } - } getOrElse NotFound - }) - - private def checkConflict(userName: String, repositoryName: String, branch: String, - requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = { - val remote = getRepositoryDir(userName, repositoryName) - val tmpdir = new java.io.File(getTemporaryDir(userName, repositoryName), "merge-check") - val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).call - try { - git.checkout.setName(branch).call - - git.fetch - .setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString) - .setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/heads/${requestBranch}")).call - - val result = git.merge - .include(git.getRepository.resolve("FETCH_HEAD")) - .setCommit(false).call - - result.getConflicts != null - - } finally { - git.getRepository.close - FileUtils.deleteDirectory(tmpdir) } } diff --git a/src/main/scala/app/WikiController.scala b/src/main/scala/app/WikiController.scala index abb5873..c1c1feb 100644 --- a/src/main/scala/app/WikiController.scala +++ b/src/main/scala/app/WikiController.scala @@ -105,9 +105,10 @@ }) get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository => - val pageName = StringUtil.urlDecode(params("page")) + val pageName = StringUtil.urlDecode(params("page")) + val account = context.loginAccount.get - deleteWikiPage(repository.owner, repository.name, pageName, context.loginAccount.get.userName, s"Delete ${pageName}") + deleteWikiPage(repository.owner, repository.name, pageName, account.userName, account.mailAddress, s"Delete ${pageName}") updateLastActivityDate(repository.owner, repository.name) redirect(s"/${repository.owner}/${repository.name}/wiki") diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index bb2542b..eab05f2 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -4,10 +4,7 @@ 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,32 +28,39 @@ */ 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) +// /** +// * lock objects +// */ +// private val locks = new ConcurrentHashMap[String, Lock]() +// +// /** +// * Returns the lock object for the specified repository. +// */ +// private def getLockObject(owner: String, repository: String): Lock = synchronized { +// val key = owner + "/" + repository +// if(!locks.containsKey(key)){ +// locks.put(key, new ReentrantLock()) +// } +// 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 = { +// val lock = getLockObject(owner, repository) +// try { +// f +// } finally { +// lock.unlock() +// } +// } } @@ -64,7 +68,7 @@ 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 { @@ -132,7 +136,7 @@ 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) @@ -168,8 +172,9 @@ /** * 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) @@ -181,8 +186,7 @@ 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 } } diff --git a/src/main/scala/util/LockUtil.scala b/src/main/scala/util/LockUtil.scala new file mode 100644 index 0000000..3b6c796 --- /dev/null +++ b/src/main/scala/util/LockUtil.scala @@ -0,0 +1,36 @@ +package util + +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.locks.{ReentrantLock, Lock} + +object LockUtil { + + /** + * lock objects + */ + private val locks = new ConcurrentHashMap[String, Lock]() + + /** + * Returns the lock object for the specified repository. + */ + private def getLockObject(key: String): Lock = synchronized { + if(!locks.containsKey(key)){ + locks.put(key, new ReentrantLock()) + } + locks.get(key) + } + + /** + * Synchronizes a given function which modifies the working copy of the wiki repository. + */ + def lock[T](key: String)(f: => T): T = { + val lock = getLockObject(key) + try { + lock.lock() + f + } finally { + lock.unlock() + } + } + +}