(refs #2)Experimental implementation of forking repository.

This commit is contained in:
takezoe
2013-07-11 14:39:25 +09:00
parent 62fb968c9a
commit 5e1eb39b87
5 changed files with 102 additions and 23 deletions

View File

@@ -1 +1,15 @@
ALTER TABLE ACCOUNT ADD COLUMN IMAGE VARCHAR(100); ALTER TABLE ACCOUNT ADD COLUMN IMAGE VARCHAR(100);
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_USER_NAME VARCHAR(100);
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_REPOSITORY_NAME VARCHAR(100);
CREATE TABLE PULL_REQUEST(
PULL_REQUEST_ID INT AUTO_INCREMENT,
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL,
COMMIT_ID VARCHAR(40) NOT NULL
);
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_PK PRIMARY KEY (PULL_REQUEST_ID);
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_1 UNIQUE ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);

View File

@@ -1,7 +1,7 @@
package app package app
import util.Directory._ import util.Directory._
import util.UsersAuthenticator import util.{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
@@ -11,24 +11,31 @@ import jp.sf.amateras.scalatra.forms._
class CreateRepositoryController extends CreateRepositoryControllerBase class CreateRepositoryController extends CreateRepositoryControllerBase
with RepositoryService with AccountService with WikiService with LabelsService with ActivityService with RepositoryService with AccountService with WikiService with LabelsService with ActivityService
with UsersAuthenticator with UsersAuthenticator with ReferrerAuthenticator
/** /**
* Creates new repository. * Creates new repository.
*/ */
trait CreateRepositoryControllerBase extends ControllerBase { trait CreateRepositoryControllerBase extends ControllerBase {
self: RepositoryService with WikiService with LabelsService with ActivityService self: RepositoryService with WikiService with LabelsService with ActivityService
with UsersAuthenticator => with UsersAuthenticator with ReferrerAuthenticator =>
case class RepositoryCreationForm(name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) case class RepositoryCreationForm(name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
val form = mapping( case class ForkRepositoryForm(owner: String, name: String)
val newForm = mapping(
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, unique))), "name" -> trim(label("Repository name", text(required, maxlength(40), identifier, unique))),
"description" -> trim(label("Description" , optional(text()))), "description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type", boolean())), "isPrivate" -> trim(label("Repository Type", boolean())),
"createReadme" -> trim(label("Create README" , boolean())) "createReadme" -> trim(label("Create README" , boolean()))
)(RepositoryCreationForm.apply) )(RepositoryCreationForm.apply)
val forkForm = mapping(
"owner" -> trim(label("Repository owner", text(required))),
"name" -> trim(label("Repository name", text(required)))
)(ForkRepositoryForm.apply)
/** /**
* Show the new repository form. * Show the new repository form.
*/ */
@@ -39,7 +46,7 @@ trait CreateRepositoryControllerBase extends ControllerBase {
/** /**
* Create new repository. * Create new repository.
*/ */
post("/new", form)(usersOnly { form => post("/new", newForm)(usersOnly { form =>
val loginAccount = context.loginAccount.get val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName val loginUserName = loginAccount.userName
@@ -47,12 +54,7 @@ trait CreateRepositoryControllerBase extends ControllerBase {
createRepository(form.name, loginUserName, form.description, form.isPrivate) createRepository(form.name, loginUserName, form.description, form.isPrivate)
// Insert default labels // Insert default labels
createLabel(loginUserName, form.name, "bug", "fc2929") insertDefaultLabels(loginUserName, form.name)
createLabel(loginUserName, form.name, "duplicate", "cccccc")
createLabel(loginUserName, form.name, "enhancement", "84b6eb")
createLabel(loginUserName, form.name, "invalid", "e6e6e6")
createLabel(loginUserName, form.name, "question", "cc317c")
createLabel(loginUserName, form.name, "wontfix", "ffffff")
// Create the actual repository // Create the actual repository
val gitdir = getRepositoryDir(loginUserName, form.name) val gitdir = getRepositoryDir(loginUserName, form.name)
@@ -97,7 +99,50 @@ trait CreateRepositoryControllerBase extends ControllerBase {
// redirect to the repository // redirect to the repository
redirect("/%s/%s".format(loginUserName, form.name)) redirect("/%s/%s".format(loginUserName, 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
// TODO Is private repository cloneable?
createRepository(repository.name, loginUserName, repository.repository.description,
repository.repository.isPrivate, Some(repository.name), Some(repository.owner))
// Insert default labels
insertDefaultLabels(loginUserName, repository.name)
// clone repository actually
val git = Git.cloneRepository
.setURI(getRepositoryDir(repository.owner, repository.name).toURI.toString)
.setDirectory(getRepositoryDir(loginUserName, repository.name))
.setBare(true).call
val config = git.getRepository.getConfig
config.setBoolean("http", null, "receivepack", true)
config.save
// Create Wiki repository
// TODO Wiki repository should be cloned also!!
createWikiRepository(loginAccount, repository.name)
// TODO Record activity!!
//recordCreateRepositoryActivity(loginUserName, repositoryName, loginUserName)
}
// redirect to the repository
redirect("/%s/%s".format(loginUserName, repository.name))
})
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")
createLabel(userName, repositoryName, "enhancement", "84b6eb")
createLabel(userName, repositoryName, "invalid", "e6e6e6")
createLabel(userName, repositoryName, "question", "cc317c")
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
/** /**
* Duplicate check for the repository name. * Duplicate check for the repository name.
*/ */

View File

@@ -9,7 +9,9 @@ object Repositories extends Table[Repository]("REPOSITORY") with BasicTemplate {
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 lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") def lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE")
def * = userName ~ repositoryName ~ isPrivate ~ description.? ~ defaultBranch ~ registeredDate ~ updatedDate ~ lastActivityDate <> (Repository, Repository.unapply _) def originUserName = column[String]("ORIGIN_USER_NAME")
def originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
def * = userName ~ repositoryName ~ isPrivate ~ description.? ~ defaultBranch ~ registeredDate ~ updatedDate ~ lastActivityDate ~ originUserName.? ~ originRepositoryName.? <> (Repository, Repository.unapply _)
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
} }
@@ -22,5 +24,7 @@ case class Repository(
defaultBranch: String, defaultBranch: String,
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date, updatedDate: java.util.Date,
lastActivityDate: java.util.Date lastActivityDate: java.util.Date,
originUserName: Option[String],
originRepositoryName: Option[String]
) )

View File

@@ -15,18 +15,23 @@ trait RepositoryService { self: AccountService =>
* @param userName the user name of the repository owner * @param userName the user name of the repository owner
* @param description the repository description * @param description the repository description
* @param isPrivate the repository type (private is true, otherwise false) * @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): Unit = {
Repositories insert Repositories insert
Repository( Repository(
userName = userName, userName = userName,
repositoryName = repositoryName, repositoryName = repositoryName,
isPrivate = isPrivate, isPrivate = isPrivate,
description = description, description = description,
defaultBranch = "master", defaultBranch = "master",
registeredDate = currentDate, registeredDate = currentDate,
updatedDate = currentDate, updatedDate = currentDate,
lastActivityDate = currentDate) lastActivityDate = currentDate,
originUserName = originUserName,
originRepositoryName = originRepositoryName)
IssueId insert (userName, repositoryName, 0) IssueId insert (userName, repositoryName, 0)
} }

View File

@@ -6,6 +6,7 @@
@if(repository.repository.isPrivate){ @if(repository.repository.isPrivate){
<i class="icon-lock"></i> <i class="icon-lock"></i>
} }
<input type="button" id="fork" class="btn" value="Fork"/>
</div> </div>
<table class="global-nav box-header"> <table class="global-nav box-header">
<tr> <tr>
@@ -25,11 +26,21 @@
} }
</tr> </tr>
</table> </table>
<form method="POST" id="repository_form">
<input type="hidden" name="owner" value="@repository.owner"/>
<input type="hidden" name="name" value="@repository.name"/>
</form>
<script type="text/javascript"> <script type="text/javascript">
$(function(){ $(function(){
$('table.global-nav th.box-header').click(function(){ $('table.global-nav th.box-header').click(function(){
location.href = $(this).find('a').attr('href'); location.href = $(this).find('a').attr('href');
return false; return false;
}); });
$('#fork').click(function(){
var form = $('form#repository_form');
form.attr('action', '@path/_fork');
form.submit();
});
}); });
</script> </script>