From 43d19d7d52bb00dd38574bf56eaece2647a180e4 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 4 Dec 2017 01:31:06 +0900 Subject: [PATCH 1/7] Add create new repository from existing git repository option --- .../gitbucket/core/account/newrepo.scala.html | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/twirl/gitbucket/core/account/newrepo.scala.html b/src/main/twirl/gitbucket/core/account/newrepo.scala.html index 6f6397b9c..d094a5557 100644 --- a/src/main/twirl/gitbucket/core/account/newrepo.scala.html +++ b/src/main/twirl/gitbucket/core/account/newrepo.scala.html @@ -39,7 +39,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
- +
-
+
@@ -83,4 +98,8 @@ $('#owner-dropdown a').click(function(){ $('#owner-dropdown span.strong').html($(this).find('span').html()); }); + +$('input[name=createRepository]').click(function(){ + $('#existingRepositoryUrl').prop('disabled', $('input[name=createRepository]:checked').val() != 'COPY'); +}); From 13800a7023ca51b790d2d56d6f4503528a654e52 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 4 Dec 2017 13:59:53 +0900 Subject: [PATCH 2/7] Implement repository cloning --- .../core/controller/AccountController.scala | 15 +++++----- .../service/RepositoryCreationService.scala | 28 ++++++++++++++++--- .../gitbucket/core/account/newrepo.scala.html | 12 ++++---- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 04455ea98..09313ec5d 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -87,15 +87,16 @@ trait AccountControllerBase extends AccountManagementControllerBase { "clearImage" -> trim(label("Clear image" ,boolean())) )(EditGroupForm.apply) - case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) + case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, initOption: String, sourceUrl: Option[String]) case class ForkRepositoryForm(owner: String, name: String) val newRepositoryForm = mapping( - "owner" -> trim(label("Owner" , text(required, maxlength(100), identifier, existsAccount))), - "name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))), - "description" -> trim(label("Description" , optional(text()))), - "isPrivate" -> trim(label("Repository Type", boolean())), - "createReadme" -> trim(label("Create README" , boolean())) + "owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))), + "name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))), + "description" -> trim(label("Description", optional(text()))), + "isPrivate" -> trim(label("Repository Type", boolean())), + "initOption" -> trim(label("Initialize option", text(required))), + "sourceUrl" -> trim(label("Source git repository URL", optional(text()))) // TODO required if initOption is "COPY" )(RepositoryCreationForm.apply) val forkRepositoryForm = mapping( @@ -529,7 +530,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { LockUtil.lock(s"${form.owner}/${form.name}"){ if(getRepository(form.owner, form.name).isEmpty){ // Create the repository - createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.createReadme) + createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.initOption, form.sourceUrl) // Call hooks PluginRegistry().getRepositoryHooks.foreach(_.created(form.owner, form.name)) diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala index 2aa419649..e32e99ccf 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala @@ -1,19 +1,27 @@ package gitbucket.core.service +import java.nio.file.Files + import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Directory._ import gitbucket.core.util.JGitUtil import gitbucket.core.model.Account +import org.apache.commons.io.FileUtils import org.eclipse.jgit.api.Git import org.eclipse.jgit.dircache.DirCache -import org.eclipse.jgit.lib.{FileMode, Constants} +import org.eclipse.jgit.lib.{Constants, FileMode} trait RepositoryCreationService { self: AccountService with RepositoryService with LabelsService with WikiService with ActivityService with PrioritiesService => - def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) - (implicit s: Session) { + def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], + isPrivate: Boolean, createReadme: Boolean)(implicit s: Session): Unit = { + createRepository(loginAccount, owner, name, description, isPrivate, if (createReadme) "README" else "EMPTY", None) + } + + def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], + isPrivate: Boolean, initOption: String, sourceUrl: Option[String])(implicit s: Session): Unit = { val ownerAccount = getAccountByUserName(owner).get val loginUserName = loginAccount.userName @@ -37,7 +45,7 @@ trait RepositoryCreationService { val gitdir = getRepositoryDir(owner, name) JGitUtil.initRepository(gitdir) - if(createReadme){ + if (initOption == "README") { using(Git.open(gitdir)){ git => val builder = DirCache.newInCore.builder() val inserter = git.getRepository.newObjectInserter() @@ -61,6 +69,18 @@ trait RepositoryCreationService { } } + if (initOption == "COPY") { + sourceUrl.foreach { url => + val dir = Files.createTempDirectory(s"gitbucket-${owner}-${name}").toFile + println("Cloning to " + dir.getAbsolutePath) + Git.cloneRepository().setBare(true).setURI(url).setDirectory(dir).setCloneAllBranches(true).call() + using(Git.open(dir)) { git => + git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call() + } + FileUtils.deleteQuietly(dir) + } + } + // Create Wiki repository createWikiRepository(loginAccount, owner, name) diff --git a/src/main/twirl/gitbucket/core/account/newrepo.scala.html b/src/main/twirl/gitbucket/core/account/newrepo.scala.html index d094a5557..d64531790 100644 --- a/src/main/twirl/gitbucket/core/account/newrepo.scala.html +++ b/src/main/twirl/gitbucket/core/account/newrepo.scala.html @@ -59,28 +59,28 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
- +
@@ -99,7 +99,7 @@ $('#owner-dropdown a').click(function(){ $('#owner-dropdown span.strong').html($(this).find('span').html()); }); -$('input[name=createRepository]').click(function(){ - $('#existingRepositoryUrl').prop('disabled', $('input[name=createRepository]:checked').val() != 'COPY'); +$('input[name=initOption]').click(function () { + $('#sourceUrl').prop('disabled', $('input[name=initOption]:checked').val() != 'COPY'); }); From efd5a64749b87a510fd5061162e432322604282c Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 4 Dec 2017 16:12:43 +0900 Subject: [PATCH 3/7] Asynchronize repository creation --- .../core/controller/AccountController.scala | 14 +- .../core/controller/ApiController.scala | 8 +- .../RepositoryViewerController.scala | 21 ++- .../service/RepositoryCreationService.scala | 138 +++++++++++------- .../gitbucket/core/repo/creating.scala.html | 26 ++++ 5 files changed, 142 insertions(+), 65 deletions(-) create mode 100644 src/main/twirl/gitbucket/core/repo/creating.scala.html diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 09313ec5d..ebf79711f 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -2,7 +2,7 @@ package gitbucket.core.controller import gitbucket.core.account.html import gitbucket.core.helper -import gitbucket.core.model.{AccountWebHook, GroupMember, RepositoryWebHook, RepositoryWebHookEvent, Role, WebHook, WebHookContentType} +import gitbucket.core.model.{AccountWebHook, GroupMember, RepositoryWebHook, Role, WebHook, WebHookContentType} import gitbucket.core.plugin.PluginRegistry import gitbucket.core.service._ import gitbucket.core.service.WebHookService._ @@ -17,6 +17,8 @@ import org.scalatra.i18n.Messages import org.scalatra.BadRequest import org.scalatra.forms._ +import scala.concurrent.ExecutionContext.Implicits.global + class AccountController extends AccountControllerBase with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator @@ -462,7 +464,6 @@ trait AccountControllerBase extends AccountManagementControllerBase { get("/:groupName/_editgroup")(managersOnly { defining(params("groupName")){ groupName => - // TODO Don't use Option.get getAccountByUserName(groupName, true).map { account => html.editgroup(account, getGroupMembers(groupName), flash.get("info")) } getOrElse NotFound() @@ -530,10 +531,11 @@ trait AccountControllerBase extends AccountManagementControllerBase { LockUtil.lock(s"${form.owner}/${form.name}"){ if(getRepository(form.owner, form.name).isEmpty){ // Create the repository - createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.initOption, form.sourceUrl) - - // Call hooks - PluginRegistry().getRepositoryHooks.foreach(_.created(form.owner, form.name)) + val f = createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.initOption, form.sourceUrl).map { _ => + // Call hooks + PluginRegistry().getRepositoryHooks.foreach(_.created(form.owner, form.name)) + } + //Await.result(f, Duration.Inf) } } diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 119992146..d7a6e75cf 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -16,6 +16,8 @@ import org.eclipse.jgit.revwalk.RevWalk import org.scalatra.{Created, NoContent, UnprocessableEntity} import scala.collection.JavaConverters._ +import scala.concurrent.Await +import scala.concurrent.duration.Duration class ApiController extends ApiControllerBase with RepositoryService @@ -249,7 +251,8 @@ trait ApiControllerBase extends ControllerBase { } yield { LockUtil.lock(s"${owner}/${data.name}") { if(getRepository(owner, data.name).isEmpty){ - createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init) + val f = createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init) + Await.result(f, Duration.Inf) val repository = getRepository(owner, data.name).get JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get))) } else { @@ -273,7 +276,8 @@ trait ApiControllerBase extends ControllerBase { } yield { LockUtil.lock(s"${groupName}/${data.name}") { if(getRepository(groupName, data.name).isEmpty){ - createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init) + val f = createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init) + Await.result(f, Duration.Inf) val repository = getRepository(groupName, data.name).get JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get))) } else { diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 311dbaca9..1fc39bf56 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -148,14 +148,27 @@ trait RepositoryViewerControllerBase extends ControllerBase { * Displays the file list of the repository root and the default branch. */ get("/:owner/:repository") { - params.get("go-get") match { - case Some("1") => defining(request.paths){ paths => - getRepository(paths(0), paths(1)).map(gitbucket.core.html.goget(_))getOrElse NotFound() + val owner = params("owner") + val repository = params("repository") + + if (RepositoryCreationService.isCreating(owner, repository)) { + gitbucket.core.repo.html.creating(owner, repository) + } else { + params.get("go-get") match { + case Some("1") => defining(request.paths) { paths => + getRepository(owner, repository).map(gitbucket.core.html.goget(_)) getOrElse NotFound() + } + case _ => referrersOnly(fileList(_)) } - case _ => referrersOnly(fileList(_)) } } + ajaxGet("/:owner/:repository/creating") { + val owner = params("owner") + val repository = params("repository") + RepositoryCreationService.isCreating(owner, repository) + } + /** * Displays the file list of the specified path and branch. */ diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala index e32e99ccf..9b5ef3195 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala @@ -1,91 +1,123 @@ package gitbucket.core.service import java.nio.file.Files +import java.util.concurrent.ConcurrentHashMap import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Directory._ import gitbucket.core.util.JGitUtil import gitbucket.core.model.Account +import gitbucket.core.servlet.Database import org.apache.commons.io.FileUtils import org.eclipse.jgit.api.Git import org.eclipse.jgit.dircache.DirCache import org.eclipse.jgit.lib.{Constants, FileMode} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +object RepositoryCreationService { + + private val Creating = new ConcurrentHashMap[String, Boolean]() + + def isCreating(owner: String, repository: String): Boolean = { + Creating.containsKey(s"${owner}/${repository}") + } + + def startCreation(owner: String, repository: String): Unit = { + Creating.put(s"${owner}/${repository}", true) + } + + def endCreation(owner: String, repository: String): Unit = { + Creating.remove(s"${owner}/${repository}") + } + +} + trait RepositoryCreationService { self: AccountService with RepositoryService with LabelsService with WikiService with ActivityService with PrioritiesService => def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], - isPrivate: Boolean, createReadme: Boolean)(implicit s: Session): Unit = { + isPrivate: Boolean, createReadme: Boolean): Future[Unit] = { createRepository(loginAccount, owner, name, description, isPrivate, if (createReadme) "README" else "EMPTY", None) } def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], - isPrivate: Boolean, initOption: String, sourceUrl: Option[String])(implicit s: Session): Unit = { - val ownerAccount = getAccountByUserName(owner).get - val loginUserName = loginAccount.userName + isPrivate: Boolean, initOption: String, sourceUrl: Option[String]): Future[Unit] = Future { + RepositoryCreationService.startCreation(owner, name) + try { + Database() withTransaction { implicit session => + val ownerAccount = getAccountByUserName(owner).get + val loginUserName = loginAccount.userName - // Insert to the database at first - insertRepository(name, owner, description, isPrivate) + // Insert to the database at first + insertRepository(name, owner, description, isPrivate) -// // Add collaborators for group repository -// if(ownerAccount.isGroupAccount){ -// getGroupMembers(owner).foreach { member => -// addCollaborator(owner, name, member.userName) -// } -// } + // // Add collaborators for group repository + // if(ownerAccount.isGroupAccount){ + // getGroupMembers(owner).foreach { member => + // addCollaborator(owner, name, member.userName) + // } + // } - // Insert default labels - insertDefaultLabels(owner, name) + // Insert default labels + insertDefaultLabels(owner, name) - // Insert default priorities - insertDefaultPriorities(owner, name) + // Insert default priorities + insertDefaultPriorities(owner, name) - // Create the actual repository - val gitdir = getRepositoryDir(owner, name) - JGitUtil.initRepository(gitdir) + // Create the actual repository + val gitdir = getRepositoryDir(owner, name) + JGitUtil.initRepository(gitdir) - if (initOption == "README") { - using(Git.open(gitdir)){ git => - val builder = DirCache.newInCore.builder() - val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - val content = if(description.nonEmpty){ - name + "\n" + - "===============\n" + - "\n" + - description.get - } else { - name + "\n" + - "===============\n" + if (initOption == "README") { + using(Git.open(gitdir)) { git => + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") + val content = if (description.nonEmpty) { + name + "\n" + + "===============\n" + + "\n" + + description.get + } else { + name + "\n" + + "===============\n" + } + + builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) + builder.finish() + + JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), + Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit") + } } - builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE, - inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) - builder.finish() + if (initOption == "COPY") { + sourceUrl.foreach { url => + // TODO How to feedback error in this block? + val dir = Files.createTempDirectory(s"gitbucket-${owner}-${name}").toFile - JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit") - } - } + Git.cloneRepository().setBare(true).setURI(url).setDirectory(dir).setCloneAllBranches(true).call() + using(Git.open(dir)) { git => + git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call() + } - if (initOption == "COPY") { - sourceUrl.foreach { url => - val dir = Files.createTempDirectory(s"gitbucket-${owner}-${name}").toFile - println("Cloning to " + dir.getAbsolutePath) - Git.cloneRepository().setBare(true).setURI(url).setDirectory(dir).setCloneAllBranches(true).call() - using(Git.open(dir)) { git => - git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call() + FileUtils.deleteQuietly(dir) + } } - FileUtils.deleteQuietly(dir) + + // Create Wiki repository + createWikiRepository(loginAccount, owner, name) + + // Record activity + recordCreateRepositoryActivity(owner, name, loginUserName) } + } finally { + RepositoryCreationService.endCreation(owner, name) } - - // Create Wiki repository - createWikiRepository(loginAccount, owner, name) - - // Record activity - recordCreateRepositoryActivity(owner, name, loginUserName) } def insertDefaultLabels(userName: String, repositoryName: String)(implicit s: Session): Unit = { diff --git a/src/main/twirl/gitbucket/core/repo/creating.scala.html b/src/main/twirl/gitbucket/core/repo/creating.scala.html new file mode 100644 index 000000000..7f695cf4b --- /dev/null +++ b/src/main/twirl/gitbucket/core/repo/creating.scala.html @@ -0,0 +1,26 @@ +@(owner: String, repository: String)(implicit context: gitbucket.core.controller.Context) +@gitbucket.core.html.main("Creating...") { +
+
+
+

Creating repository...

+ +
+
+
+} + From c4dc1d733450beab7841de16c63b77ac6e371d9c Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 4 Dec 2017 16:42:15 +0900 Subject: [PATCH 4/7] Feedback error in repository creation to users --- .../RepositoryViewerController.scala | 14 ++++-- .../service/RepositoryCreationService.scala | 46 +++++++++++++------ .../gitbucket/core/repo/creating.scala.html | 10 +++- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 1fc39bf56..8fb6370bf 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -25,6 +25,7 @@ import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder} import org.eclipse.jgit.errors.MissingObjectException import org.eclipse.jgit.lib._ import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack} +import org.json4s.jackson.Serialization import org.scalatra._ import org.scalatra.i18n.Messages @@ -166,7 +167,11 @@ trait RepositoryViewerControllerBase extends ControllerBase { ajaxGet("/:owner/:repository/creating") { val owner = params("owner") val repository = params("repository") - RepositoryCreationService.isCreating(owner, repository) + contentType = formats("json") + Serialization.write(Map( + "creating" -> RepositoryCreationService.isCreating(owner, repository), + "error" -> RepositoryCreationService.getCreationError(owner, repository) + )) } /** @@ -416,7 +421,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { contentType = formats("json") using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name - Map( + Serialization.write(Map( "root" -> s"${context.baseUrl}/${repository.owner}/${repository.name}", "id" -> id, "path" -> path, @@ -431,8 +436,9 @@ trait RepositoryViewerControllerBase extends ControllerBase { "prevPath" -> blame.prevPath, "commited" -> blame.commitTime.getTime, "message" -> blame.message, - "lines" -> blame.lines) - }) + "lines" -> blame.lines + ) + })) } }) diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala index 9b5ef3195..cdb3e591f 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala @@ -19,18 +19,25 @@ import scala.concurrent.Future object RepositoryCreationService { - private val Creating = new ConcurrentHashMap[String, Boolean]() + private val Creating = new ConcurrentHashMap[String, Option[String]]() def isCreating(owner: String, repository: String): Boolean = { - Creating.containsKey(s"${owner}/${repository}") + Option(Creating.get(s"${owner}/${repository}")).map(_.isEmpty).getOrElse(false) } def startCreation(owner: String, repository: String): Unit = { - Creating.put(s"${owner}/${repository}", true) + Creating.put(s"${owner}/${repository}", None) } - def endCreation(owner: String, repository: String): Unit = { - Creating.remove(s"${owner}/${repository}") + def endCreation(owner: String, repository: String, error: Option[String]): Unit = { + error match { + case None => Creating.remove(s"${owner}/${repository}") + case Some(error) => Creating.put(s"${owner}/${repository}", Some(error)) + } + } + + def getCreationError(owner: String, repository: String): Option[String] = { + Option(Creating.get(s"${owner}/${repository}")).getOrElse(None) } } @@ -51,6 +58,15 @@ trait RepositoryCreationService { val ownerAccount = getAccountByUserName(owner).get val loginUserName = loginAccount.userName + val copyRepositoryDir = if (initOption == "COPY") { + sourceUrl.flatMap { url => + val dir = Files.createTempDirectory(s"gitbucket-${owner}-${name}").toFile + Git.cloneRepository().setBare(true).setURI(url).setDirectory(dir).setCloneAllBranches(true).call() + Some(dir) + } + } else None + + // Insert to the database at first insertRepository(name, owner, description, isPrivate) @@ -95,16 +111,12 @@ trait RepositoryCreationService { } } - if (initOption == "COPY") { - sourceUrl.foreach { url => - // TODO How to feedback error in this block? - val dir = Files.createTempDirectory(s"gitbucket-${owner}-${name}").toFile - - Git.cloneRepository().setBare(true).setURI(url).setDirectory(dir).setCloneAllBranches(true).call() + copyRepositoryDir.foreach { dir => + try { using(Git.open(dir)) { git => git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call() } - + } finally { FileUtils.deleteQuietly(dir) } } @@ -115,8 +127,14 @@ trait RepositoryCreationService { // Record activity recordCreateRepositoryActivity(owner, name, loginUserName) } - } finally { - RepositoryCreationService.endCreation(owner, name) + + RepositoryCreationService.endCreation(owner, name, None) + + } catch { + case ex: Exception => { + ex.printStackTrace() + RepositoryCreationService.endCreation(owner, name, Some(ex.toString)) + } } } diff --git a/src/main/twirl/gitbucket/core/repo/creating.scala.html b/src/main/twirl/gitbucket/core/repo/creating.scala.html index 7f695cf4b..8c4e753ef 100644 --- a/src/main/twirl/gitbucket/core/repo/creating.scala.html +++ b/src/main/twirl/gitbucket/core/repo/creating.scala.html @@ -16,10 +16,16 @@ function checkCreating() { $.get('@context.path/@owner/@repository/creating', function (data) { - if (data == 'true') { + console.log(data); + if (data.creating == true) { setTimeout(checkCreating, 2000); } else { - location.href = '@context.path/@owner/@repository'; + if (data.error) { + alert(data.error); + location.href = '@context.path/new'; + } else { + location.href = '@context.path/@owner/@repository'; + } } }); } From f6a048e0f78066f03e9f936e6d6acece1cfc9099 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 4 Dec 2017 19:59:48 +0900 Subject: [PATCH 5/7] FeedAdd validation for sourceUrl --- .../scala/gitbucket/core/controller/AccountController.scala | 2 +- src/main/twirl/gitbucket/core/account/newrepo.scala.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index ebf79711f..239bdf6af 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -98,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { "description" -> trim(label("Description", optional(text()))), "isPrivate" -> trim(label("Repository Type", boolean())), "initOption" -> trim(label("Initialize option", text(required))), - "sourceUrl" -> trim(label("Source git repository URL", optional(text()))) // TODO required if initOption is "COPY" + "sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text()))) )(RepositoryCreationForm.apply) val forkRepositoryForm = mapping( diff --git a/src/main/twirl/gitbucket/core/account/newrepo.scala.html b/src/main/twirl/gitbucket/core/account/newrepo.scala.html index d64531790..32ae64520 100644 --- a/src/main/twirl/gitbucket/core/account/newrepo.scala.html +++ b/src/main/twirl/gitbucket/core/account/newrepo.scala.html @@ -81,6 +81,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C +
From 58635674cb852174c5cb4f05c37d03f515bb2238 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 4 Dec 2017 20:18:39 +0900 Subject: [PATCH 6/7] Asynchronize repository forking --- .../core/controller/AccountController.scala | 79 +++---------------- .../service/RepositoryCreationService.scala | 75 ++++++++++++++++-- 2 files changed, 80 insertions(+), 74 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 239bdf6af..7c2a8f469 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -12,13 +12,10 @@ import gitbucket.core.util.Directory._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.StringUtil._ import gitbucket.core.util._ -import org.apache.commons.io.FileUtils import org.scalatra.i18n.Messages import org.scalatra.BadRequest import org.scalatra.forms._ -import scala.concurrent.ExecutionContext.Implicits.global - class AccountController extends AccountControllerBase with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator @@ -530,12 +527,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { post("/new", newRepositoryForm)(usersOnly { form => LockUtil.lock(s"${form.owner}/${form.name}"){ if(getRepository(form.owner, form.name).isEmpty){ - // Create the repository - val f = createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.initOption, form.sourceUrl).map { _ => - // Call hooks - PluginRegistry().getRepositoryHooks.foreach(_.created(form.owner, form.name)) - } - //Await.result(f, Duration.Inf) + createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.initOption, form.sourceUrl) } } @@ -569,66 +561,15 @@ trait AccountControllerBase extends AccountManagementControllerBase { val loginUserName = loginAccount.userName val accountName = form.accountName - LockUtil.lock(s"${accountName}/${repository.name}"){ - if(getRepository(accountName, repository.name).isDefined || - (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){ - // redirect to the repository if repository already exists - redirect(s"/${accountName}/${repository.name}") - } else { - // Insert to the database at first - val originUserName = repository.repository.originUserName.getOrElse(repository.owner) - val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) - - insertRepository( - repositoryName = repository.name, - userName = accountName, - description = repository.repository.description, - isPrivate = repository.repository.isPrivate, - originRepositoryName = Some(originRepositoryName), - originUserName = Some(originUserName), - parentRepositoryName = Some(repository.name), - parentUserName = Some(repository.owner) - ) - - // Set default collaborators for the private fork - if(repository.repository.isPrivate){ - // Copy collaborators from the source repository - getCollaborators(repository.owner, repository.name).foreach { case (collaborator, _) => - addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role) - } - // Register an owner of the source repository as a collaborator - addCollaborator(accountName, repository.name, repository.owner, Role.ADMIN.name) - } - - // Insert default labels - insertDefaultLabels(accountName, repository.name) - // Insert default priorities - insertDefaultPriorities(accountName, repository.name) - - // clone repository actually - JGitUtil.cloneRepository( - getRepositoryDir(repository.owner, repository.name), - FileUtil.deleteIfExists(getRepositoryDir(accountName, repository.name))) - - // Create Wiki repository - JGitUtil.cloneRepository(getWikiRepositoryDir(repository.owner, repository.name), - FileUtil.deleteIfExists(getWikiRepositoryDir(accountName, repository.name))) - - // Copy LFS files - val lfsDir = getLfsDir(repository.owner, repository.name) - if(lfsDir.exists){ - FileUtils.copyDirectory(lfsDir, FileUtil.deleteIfExists(getLfsDir(accountName, repository.name))) - } - - // Record activity - recordForkActivity(repository.owner, repository.name, loginUserName, accountName) - - // Call hooks - PluginRegistry().getRepositoryHooks.foreach(_.forked(repository.owner, accountName, repository.name)) - - // redirect to the repository - redirect(s"/${accountName}/${repository.name}") - } + if (getRepository(accountName, repository.name).isDefined || + (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) { + // redirect to the repository if repository already exists + redirect(s"/${accountName}/${repository.name}") + } else { + // fork repository asynchronously + forkRepository(accountName, repository, loginUserName) + // redirect to the repository + redirect(s"/${accountName}/${repository.name}") } } else BadRequest() }) diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala index cdb3e591f..65e95a91c 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala @@ -6,8 +6,10 @@ import java.util.concurrent.ConcurrentHashMap import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Directory._ -import gitbucket.core.util.JGitUtil -import gitbucket.core.model.Account +import gitbucket.core.util.{FileUtil, JGitUtil, LockUtil} +import gitbucket.core.model.{Account, Role} +import gitbucket.core.plugin.PluginRegistry +import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.servlet.Database import org.apache.commons.io.FileUtils import org.eclipse.jgit.api.Git @@ -126,15 +128,78 @@ trait RepositoryCreationService { // Record activity recordCreateRepositoryActivity(owner, name, loginUserName) + + // Call hooks + PluginRegistry().getRepositoryHooks.foreach(_.created(owner, name)) } RepositoryCreationService.endCreation(owner, name, None) } catch { - case ex: Exception => { - ex.printStackTrace() - RepositoryCreationService.endCreation(owner, name, Some(ex.toString)) + case ex: Exception => RepositoryCreationService.endCreation(owner, name, Some(ex.toString)) + } + } + + def forkRepository(accountName: String, repository: RepositoryInfo, loginUserName: String): Future[Unit] = Future { + RepositoryCreationService.startCreation(accountName, repository.name) + try { + LockUtil.lock(s"${accountName}/${repository.name}") { + Database() withTransaction { implicit session => + val originUserName = repository.repository.originUserName.getOrElse(repository.owner) + val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) + + insertRepository( + repositoryName = repository.name, + userName = accountName, + description = repository.repository.description, + isPrivate = repository.repository.isPrivate, + originRepositoryName = Some(originRepositoryName), + originUserName = Some(originUserName), + parentRepositoryName = Some(repository.name), + parentUserName = Some(repository.owner) + ) + + // Set default collaborators for the private fork + if (repository.repository.isPrivate) { + // Copy collaborators from the source repository + getCollaborators(repository.owner, repository.name).foreach { case (collaborator, _) => + addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role) + } + // Register an owner of the source repository as a collaborator + addCollaborator(accountName, repository.name, repository.owner, Role.ADMIN.name) + } + + // Insert default labels + insertDefaultLabels(accountName, repository.name) + // Insert default priorities + insertDefaultPriorities(accountName, repository.name) + + // clone repository actually + JGitUtil.cloneRepository( + getRepositoryDir(repository.owner, repository.name), + FileUtil.deleteIfExists(getRepositoryDir(accountName, repository.name))) + + // Create Wiki repository + JGitUtil.cloneRepository(getWikiRepositoryDir(repository.owner, repository.name), + FileUtil.deleteIfExists(getWikiRepositoryDir(accountName, repository.name))) + + // Copy LFS files + val lfsDir = getLfsDir(repository.owner, repository.name) + if (lfsDir.exists) { + FileUtils.copyDirectory(lfsDir, FileUtil.deleteIfExists(getLfsDir(accountName, repository.name))) + } + + // Record activity + recordForkActivity(repository.owner, repository.name, loginUserName, accountName) + + // Call hooks + PluginRegistry().getRepositoryHooks.foreach(_.forked(repository.owner, accountName, repository.name)) + + RepositoryCreationService.endCreation(accountName, repository.name, None) + } } + } catch { + case ex: Exception => RepositoryCreationService.endCreation(accountName, repository.name, Some(ex.toString)) } } From d157426d66d4387dd1adca65549fc4d8509719b8 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 4 Dec 2017 20:55:11 +0900 Subject: [PATCH 7/7] Fix error page for repository creation --- .../core/service/RepositoryCreationService.scala | 2 +- .../twirl/gitbucket/core/repo/creating.scala.html | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala index 65e95a91c..7b9bffd79 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala @@ -39,7 +39,7 @@ object RepositoryCreationService { } def getCreationError(owner: String, repository: String): Option[String] = { - Option(Creating.get(s"${owner}/${repository}")).getOrElse(None) + Option(Creating.remove(s"${owner}/${repository}")).getOrElse(None) } } diff --git a/src/main/twirl/gitbucket/core/repo/creating.scala.html b/src/main/twirl/gitbucket/core/repo/creating.scala.html index 8c4e753ef..715a5b59c 100644 --- a/src/main/twirl/gitbucket/core/repo/creating.scala.html +++ b/src/main/twirl/gitbucket/core/repo/creating.scala.html @@ -2,10 +2,16 @@ @gitbucket.core.html.main("Creating...") {
-
+ +

Creating repository...

+ +
} @@ -21,8 +27,9 @@ setTimeout(checkCreating, 2000); } else { if (data.error) { - alert(data.error); - location.href = '@context.path/new'; + $('#errorMessage').text(data.error); + $('#error').show(); + $('#progress').hide(); } else { location.href = '@context.path/@owner/@repository'; }