Add lock for repository operation.

This commit is contained in:
takezoe
2013-07-26 18:14:31 +09:00
parent 2f52ed3ee0
commit 1af52d16c0
5 changed files with 243 additions and 188 deletions

View File

@@ -1,7 +1,7 @@
package app package app
import util.Directory._ import util.Directory._
import util.{JGitUtil, UsersAuthenticator, ReferrerAuthenticator} import util.{LockUtil, JGitUtil, UsersAuthenticator, ReferrerAuthenticator}
import service._ import service._
import java.io.File import java.io.File
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
@@ -48,119 +48,125 @@ trait CreateRepositoryControllerBase extends ControllerBase {
* Create new repository. * Create new repository.
*/ */
post("/new", newForm)(usersOnly { form => post("/new", newForm)(usersOnly { form =>
val ownerAccount = getAccountByUserName(form.owner).get LockUtil.lock(s"${form.owner}/${form.name}/create"){
val loginAccount = context.loginAccount.get if(getRepository(form.owner, form.name, baseUrl).isEmpty){
val loginUserName = loginAccount.userName val ownerAccount = getAccountByUserName(form.owner).get
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
// Insert to the database at first // Insert to the database at first
createRepository(form.name, form.owner, form.description, form.isPrivate) createRepository(form.name, form.owner, form.description, form.isPrivate)
// Add collaborators for group repository // Add collaborators for group repository
if(ownerAccount.isGroupAccount){ if(ownerAccount.isGroupAccount){
getGroupMembers(form.owner).foreach { userName => getGroupMembers(form.owner).foreach { userName =>
addCollaborator(form.owner, form.name, 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 => post("/:owner/:repository/_fork")(referrersOnly { repository =>
val loginAccount = context.loginAccount.get val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName val loginUserName = loginAccount.userName
if(getRepository(loginUserName, repository.name, baseUrl).isEmpty){ LockUtil.lock(s"${loginUserName}/${repository.name}/create"){
// Insert to the database at first if(getRepository(loginUserName, repository.name, baseUrl).isEmpty){
val originUserName = repository.repository.originUserName.getOrElse(repository.owner) // Insert to the database at first
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
createRepository( createRepository(
repositoryName = repository.name, repositoryName = repository.name,
userName = loginUserName, userName = loginUserName,
description = repository.repository.description, description = repository.repository.description,
isPrivate = repository.repository.isPrivate, isPrivate = repository.repository.isPrivate,
originRepositoryName = Some(originRepositoryName), originRepositoryName = Some(originRepositoryName),
originUserName = Some(originUserName), originUserName = Some(originUserName),
parentRepositoryName = Some(repository.name), parentRepositoryName = Some(repository.name),
parentUserName = Some(repository.owner) parentUserName = Some(repository.owner)
) )
// Insert default labels // Insert default labels
insertDefaultLabels(loginUserName, repository.name) insertDefaultLabels(loginUserName, repository.name)
// clone repository actually // clone repository actually
JGitUtil.cloneRepository( JGitUtil.cloneRepository(
getRepositoryDir(repository.owner, repository.name), getRepositoryDir(repository.owner, repository.name),
getRepositoryDir(loginUserName, repository.name)) getRepositoryDir(loginUserName, repository.name))
// Create Wiki repository // Create Wiki repository
JGitUtil.cloneRepository( JGitUtil.cloneRepository(
getWikiRepositoryDir(repository.owner, repository.name), getWikiRepositoryDir(repository.owner, repository.name),
getWikiRepositoryDir(loginUserName, repository.name)) getWikiRepositoryDir(loginUserName, repository.name))
// insert commit id // insert commit id
JGitUtil.withGit(getRepositoryDir(loginUserName, repository.name)){ git => JGitUtil.withGit(getRepositoryDir(loginUserName, repository.name)){ git =>
JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch => JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch =>
JGitUtil.getCommitLog(git, branch) match { JGitUtil.getCommitLog(git, branch) match {
case Right((commits, _)) => commits.foreach { commit => case Right((commits, _)) => commits.foreach { commit =>
if(!existsCommitId(loginUserName, repository.name, commit.id)){ if(!existsCommitId(loginUserName, repository.name, commit.id)){
insertCommitId(loginUserName, repository.name, commit.id) insertCommitId(loginUserName, repository.name, commit.id)
}
} }
case Left(_) => ???
} }
case Left(_) => ???
} }
} }
}
// Record activity // Record activity
recordForkActivity(repository.owner, repository.name, loginUserName) 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 = { private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {

View File

@@ -1,6 +1,6 @@
package app package app
import util.{CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator} import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator}
import util.Directory._ import util.Directory._
import util.Implicits._ import util.Implicits._
import util.JGitUtil.{DiffInfo, CommitInfo} import util.JGitUtil.{DiffInfo, CommitInfo}
@@ -82,77 +82,85 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/pulls/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) => 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) => getPullRequest(repository.owner, repository.name, issueId).map { case (issue, pullreq) =>
val remote = getRepositoryDir(repository.owner, repository.name) val remote = getRepositoryDir(repository.owner, repository.name)
val tmpdir = new java.io.File(getTemporaryDir(repository.owner, repository.name), s"merge-${issueId}") 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 val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).call
try { try {
// TODO mark issue as 'merged' if(tmpdir.exists()){
val loginAccount = context.loginAccount.get FileUtils.deleteDirectory(tmpdir)
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Closed", "close") }
updateClosed(repository.owner, repository.name, issueId, true) git.checkout.setName(branch).call
recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, form.message)
git.checkout.setName(pullreq.branch).call
git.fetch git.fetch
.setRemote(getRepositoryDir(repository.owner, repository.name).toURI.toString) .setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString)
.setRefSpecs(new RefSpec(s"refs/pull/${issueId}/head:refs/heads/${pullreq.branch}")).call .setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/heads/${requestBranch}")).call
val result = git.merge val result = git.merge
.include(git.getRepository.resolve("FETCH_HEAD")) .include(git.getRepository.resolve("FETCH_HEAD"))
.setCommit(false).call .setCommit(false).call
if(result.getConflicts != null){ 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}")
} finally { } finally {
git.getRepository.close git.getRepository.close
FileUtils.deleteDirectory(tmpdir) 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)
} }
} }

View File

@@ -105,9 +105,10 @@ trait WikiControllerBase extends ControllerBase {
}) })
get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository => 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) updateLastActivityDate(repository.owner, repository.name)
redirect(s"/${repository.owner}/${repository.name}/wiki") redirect(s"/${repository.owner}/${repository.name}/wiki")

View File

@@ -4,10 +4,7 @@ import java.io.File
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.JGitUtil.DiffInfo import util.{Directory, JGitUtil, LockUtil}
import util.{Directory, JGitUtil}
import org.eclipse.jgit.treewalk.CanonicalTreeParser
import java.util.concurrent.ConcurrentHashMap
object WikiService { object WikiService {
@@ -31,32 +28,39 @@ object WikiService {
*/ */
case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date) case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
/** // /**
* lock objects // * lock objects
*/ // */
private val locks = new ConcurrentHashMap[String, AnyRef]() // private val locks = new ConcurrentHashMap[String, Lock]()
//
/** // /**
* Returns the lock object for the specified repository. // * Returns the lock object for the specified repository.
*/ // */
private def getLockObject(owner: String, repository: String): AnyRef = synchronized { // private def getLockObject(owner: String, repository: String): Lock = synchronized {
val key = owner + "/" + repository // val key = owner + "/" + repository
if(!locks.containsKey(key)){ // if(!locks.containsKey(key)){
locks.put(key, new AnyRef()) // locks.put(key, new ReentrantLock())
} // }
locks.get(key) // locks.get(key)
} // }
//
/** // /**
* Synchronizes a given function which modifies the working copy of the wiki repository. // * Synchronizes a given function which modifies the working copy of the wiki repository.
* // *
* @param owner the repository owner // * @param owner the repository owner
* @param repository the repository name // * @param repository the repository name
* @param f the function which modifies the working copy of the wiki repository // * @param f the function which modifies the working copy of the wiki repository
* @tparam T the return type of the given function // * @tparam T the return type of the given function
* @return the result 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) // def lock[T](owner: String, repository: String)(f: => T): T = {
// val lock = getLockObject(owner, repository)
// try {
// f
// } finally {
// lock.unlock()
// }
// }
} }
@@ -64,7 +68,7 @@ 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 = {
lock(owner, repository){ LockUtil.lock(s"${owner}/${repository}/wiki"){
val dir = Directory.getWikiRepositoryDir(owner, repository) val dir = Directory.getWikiRepositoryDir(owner, repository)
if(!dir.exists){ if(!dir.exists){
try { try {
@@ -132,7 +136,7 @@ trait WikiService {
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): Unit = { content: String, committer: model.Account, message: String): Unit = {
lock(owner, repository){ LockUtil.lock(s"${owner}/${repository}/wiki"){
// clone working copy // clone working copy
val workDir = Directory.getWikiWorkDir(owner, repository) val workDir = Directory.getWikiWorkDir(owner, repository)
cloneOrPullWorkingCopy(workDir, owner, repository) cloneOrPullWorkingCopy(workDir, owner, repository)
@@ -168,8 +172,9 @@ trait WikiService {
/** /**
* Delete the wiki page. * Delete the wiki page.
*/ */
def deleteWikiPage(owner: String, repository: String, pageName: String, committer: String, message: String): Unit = { def deleteWikiPage(owner: String, repository: String, pageName: String,
lock(owner, repository){ committer: String, mailAddress: String, message: String): Unit = {
LockUtil.lock(s"${owner}/${repository}/wiki"){
// clone working copy // clone working copy
val workDir = Directory.getWikiWorkDir(owner, repository) val workDir = Directory.getWikiWorkDir(owner, repository)
cloneOrPullWorkingCopy(workDir, owner, repository) cloneOrPullWorkingCopy(workDir, owner, repository)
@@ -181,8 +186,7 @@ trait WikiService {
git.rm.addFilepattern(pageName + ".md").call git.rm.addFilepattern(pageName + ".md").call
// commit and push // commit and push
// TODO committer's mail address git.commit.setAuthor(committer, mailAddress).setMessage(message).call
git.commit.setAuthor(committer, committer + "@devnull").setMessage(message).call
git.push.call git.push.call
} }
} }

View File

@@ -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()
}
}
}