Add function to create new branch with commit on UI and start PR (#2571)

This commit is contained in:
onukura
2020-11-03 16:04:08 +09:00
committed by GitHub
parent e91d903650
commit d97f7c6025
4 changed files with 242 additions and 89 deletions

View File

@@ -16,7 +16,8 @@ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, CommitStatus} import gitbucket.core.model.{Account, CommitState, CommitStatus}
import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.JGitUtil.{CommitInfo, createBranch}
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers import gitbucket.core.view.helpers
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream} import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
@@ -90,7 +91,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
branch: String, branch: String,
path: String, path: String,
uploadFiles: String, uploadFiles: String,
message: Option[String] message: Option[String],
commit: String,
newBranch: Boolean
) )
case class EditorForm( case class EditorForm(
@@ -102,7 +105,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
lineSeparator: String, lineSeparator: String,
newFileName: String, newFileName: String,
oldFileName: Option[String], oldFileName: Option[String],
commit: String commit: String,
newBranch: Boolean
) )
case class DeleteForm( case class DeleteForm(
@@ -110,7 +114,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
path: String, path: String,
message: Option[String], message: Option[String],
fileName: String, fileName: String,
commit: String commit: String,
newBranch: Boolean
) )
case class CommentForm( case class CommentForm(
@@ -133,6 +138,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"path" -> trim(label("Path", text())), "path" -> trim(label("Path", text())),
"uploadFiles" -> trim(label("Upload files", text(required))), "uploadFiles" -> trim(label("Upload files", text(required))),
"message" -> trim(label("Message", optional(text()))), "message" -> trim(label("Message", optional(text()))),
"commit" -> trim(label("Commit", text(required, conflict))),
"newBranch" -> trim(label("New Branch", boolean()))
)(UploadForm.apply) )(UploadForm.apply)
val editorForm = mapping( val editorForm = mapping(
@@ -144,7 +151,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"lineSeparator" -> trim(label("Line Separator", text(required))), "lineSeparator" -> trim(label("Line Separator", text(required))),
"newFileName" -> trim(label("Filename", text(required))), "newFileName" -> trim(label("Filename", text(required))),
"oldFileName" -> trim(label("Old filename", optional(text()))), "oldFileName" -> trim(label("Old filename", optional(text()))),
"commit" -> trim(label("Commit", text(required, conflict))) "commit" -> trim(label("Commit", text(required, conflict))),
"newBranch" -> trim(label("New Branch", boolean()))
)(EditorForm.apply) )(EditorForm.apply)
val deleteForm = mapping( val deleteForm = mapping(
@@ -152,7 +160,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
"path" -> trim(label("Path", text())), "path" -> trim(label("Path", text())),
"message" -> trim(label("Message", optional(text()))), "message" -> trim(label("Message", optional(text()))),
"fileName" -> trim(label("Filename", text(required))), "fileName" -> trim(label("Filename", text(required))),
"commit" -> trim(label("Commit", text(required, conflict))) "commit" -> trim(label("Commit", text(required, conflict))),
"newBranch" -> trim(label("New Branch", boolean()))
)(DeleteForm.apply) )(DeleteForm.apply)
val commentForm = mapping( val commentForm = mapping(
@@ -337,7 +346,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val (branch, path) = repository.splitPath(multiParams("splat").head) val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch) val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch)
.needStatusCheck(context.loginAccount.get.userName) .needStatusCheck(context.loginAccount.get.userName)
html.upload(branch, repository, if (path.length == 0) Nil else path.split("/").toList, protectedBranch) Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
html.upload(
branch,
repository,
if (path.length == 0) Nil else path.split("/").toList,
protectedBranch,
revCommit.name
)
}
}) })
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
@@ -350,36 +368,47 @@ trait RepositoryViewerControllerBase extends ControllerBase {
file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}") file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}")
} }
commitFiles( if (form.newBranch) {
repository = repository, val newBranchName = createNewBranchForPullRequest(repository, form.branch)
branch = form.branch, val objectId = _commit(newBranchName)
path = form.path, val issueId =
files = files.toIndexedSeq, createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
message = form.message.getOrElse("Add files via upload"), redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
loginAccount = context.loginAccount.get, } else {
settings = context.settings _commit(form.branch)
) { if (form.path.length == 0) {
case (git, headTip, builder, inserter) => redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
JGitUtil.processTree(git, headTip) { (path, tree) => } else {
if (!newFiles.exists(_.name.contains(path))) { redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}/${form.path}")
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) }
}
}
newFiles.foreach { file =>
val bytes =
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
builder.add(
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
)
builder.finish()
}
} }
if (form.path.length == 0) { def _commit(branchName: String): ObjectId = {
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}") commitFiles(
} else { repository = repository,
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}/${form.path}") branch = branchName,
path = form.path,
files = files.toIndexedSeq,
message = form.message.getOrElse("Add files via upload"),
loginAccount = context.loginAccount.get,
settings = context.settings
) {
case (git, headTip, builder, inserter) =>
JGitUtil.processTree(git, headTip) { (path, tree) =>
if (!newFiles.exists(_.name.contains(path))) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
}
newFiles.foreach { file =>
val bytes =
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
builder.add(
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
)
builder.finish()
}
}
} }
}) })
@@ -434,70 +463,156 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/create", editorForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/create", editorForm)(writableUsersOnly { (form, repository) =>
commitFile( if (form.newBranch) {
repository = repository, val newBranchName = createNewBranchForPullRequest(repository, form.branch)
branch = form.branch, val objectId = _commit(newBranchName)
path = form.path, val issueId =
newFileName = Some(form.newFileName), createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
oldFileName = None, redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), } else {
charset = form.charset, _commit(form.branch)
message = form.message.getOrElse(s"Create ${form.newFileName}"), redirect(
commit = form.commit, s"/${repository.owner}/${repository.name}/blob/${form.branch}/${if (form.path.length == 0) urlEncode(form.newFileName)
loginAccount = context.loginAccount.get, else s"${form.path}/${urlEncode(form.newFileName)}"}"
settings = context.settings )
) }
redirect( def _commit(branchName: String): ObjectId = {
s"/${repository.owner}/${repository.name}/blob/${form.branch}/${if (form.path.length == 0) urlEncode(form.newFileName) commitFile(
else s"${form.path}/${urlEncode(form.newFileName)}"}" repository = repository,
) branch = branchName,
path = form.path,
newFileName = Some(form.newFileName),
oldFileName = None,
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
charset = form.charset,
message = form.message.getOrElse(s"Create ${form.newFileName}"),
commit = form.commit,
loginAccount = context.loginAccount.get,
settings = context.settings
)
}
}) })
post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) =>
commitFile( if (form.newBranch) {
repository = repository, val newBranchName = createNewBranchForPullRequest(repository, form.branch)
branch = form.branch, val objectId = _commit(newBranchName)
path = form.path, val issueId =
newFileName = Some(form.newFileName), createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
oldFileName = form.oldFileName, redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), } else {
charset = form.charset, _commit(form.branch)
message = if (form.oldFileName.contains(form.newFileName)) { redirect(
form.message.getOrElse(s"Update ${form.newFileName}") s"/${repository.owner}/${repository.name}/blob/${urlEncode(form.branch)}/${if (form.path.length == 0) urlEncode(form.newFileName)
} else { else s"${form.path}/${urlEncode(form.newFileName)}"}"
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}") )
}, }
commit = form.commit,
loginAccount = context.loginAccount.get,
settings = context.settings
)
redirect( def _commit(branchName: String): ObjectId = {
s"/${repository.owner}/${repository.name}/blob/${urlEncode(form.branch)}/${if (form.path.length == 0) urlEncode(form.newFileName) commitFile(
else s"${form.path}/${urlEncode(form.newFileName)}"}" repository = repository,
) branch = branchName,
path = form.path,
newFileName = Some(form.newFileName),
oldFileName = form.oldFileName,
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
charset = form.charset,
message = if (form.oldFileName.contains(form.newFileName)) {
form.message.getOrElse(s"Update ${form.newFileName}")
} else {
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
},
commit = form.commit,
loginAccount = context.loginAccount.get,
settings = context.settings
)
}
}) })
post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) =>
commitFile( if (form.newBranch) {
repository = repository, val newBranchName = createNewBranchForPullRequest(repository, form.branch)
branch = form.branch, val objectId = _commit(newBranchName)
path = form.path, val issueId =
newFileName = None, createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
oldFileName = Some(form.fileName), redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
content = "", } else {
charset = "", _commit(form.branch)
message = form.message.getOrElse(s"Delete ${form.fileName}"), redirect(
commit = form.commit, s"/${repository.owner}/${repository.name}/tree/${form.branch}${if (form.path.length == 0) ""
else "/" + form.path}"
)
}
def _commit(branchName: String): ObjectId = {
commitFile(
repository = repository,
branch = branchName,
path = form.path,
newFileName = None,
oldFileName = Some(form.fileName),
content = "",
charset = "",
message = form.message.getOrElse(s"Delete ${form.fileName}"),
commit = form.commit,
loginAccount = context.loginAccount.get,
settings = context.settings
)
}
})
private def getNewBranchName(repository: RepositoryInfo): String = {
var i = 1
val branchNamePrefix = cutTail(context.loginAccount.get.userName.replaceAll("[^a-zA-Z0-9-_]", "-"), 25)
while (repository.branchList.exists(p => p.contains(s"$branchNamePrefix-patch-$i"))) {
i += 1
}
s"$branchNamePrefix-patch-$i"
}
private def createNewBranchForPullRequest(repository: RepositoryInfo, baseBranchName: String): String = {
val newBranchName = getNewBranchName(repository)
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JGitUtil.createBranch(git, baseBranchName, newBranchName)
}
newBranchName
}
private def createIssueAndPullRequest(
repository: RepositoryInfo,
baseBranch: String,
requestBranch: String,
commitIdFrom: String,
commitIdTo: String,
commitMessage: Option[String]
): Int = {
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = context.loginAccount.get.userName,
title = requestBranch,
content = commitMessage,
assignedUserName = None,
milestoneId = None,
priorityId = None,
isPullRequest = true
)
createPullRequest(
originRepository = repository,
issueId = issueId,
originBranch = baseBranch,
requestUserName = repository.owner,
requestRepositoryName = repository.name,
requestBranch = requestBranch,
commitIdFrom = commitIdFrom,
commitIdTo = commitIdTo,
isDraft = false,
loginAccount = context.loginAccount.get, loginAccount = context.loginAccount.get,
settings = context.settings settings = context.settings
) )
issueId
redirect( }
s"/${repository.owner}/${repository.name}/tree/${form.branch}${if (form.path.length == 0) "" else "/" + form.path}"
)
})
get("/:owner/:repository/raw/*")(referrersOnly { repository => get("/:owner/:repository/raw/*")(referrersOnly { repository =>
val (id, path) = repository.splitPath(multiParams("splat").head) val (id, path) = repository.splitPath(multiParams("splat").head)

View File

@@ -45,6 +45,18 @@
<div class="form-group"> <div class="form-group">
<input type="text" name="message" class="form-control"/> <input type="text" name="message" class="form-control"/>
</div> </div>
<div class="form-group">
<div class="col-md-12">
<label class="radio">
<input type="radio" id="newBranch" name="newBranch" value="false" checked>
<i class='octicon octicon-git-commit'></i><span>Commit directory to the <strong style="background-color: lightblue; font-family: Consolas">@branch</strong> branch.</span>
</label>
<label class="radio">
<input type="radio" id="newBranch" name="newBranch" value="true">
<i class='octicon octicon-git-pull-request'></i><span>Create a <strong>new branch</strong> for this commit and start a pull request.</span>
</label>
</div>
</div>
<div style="text-align: right;"> <div style="text-align: right;">
<a href="@helpers.url(repository)/blob/@helpers.encodeRefName((branch :: pathList ::: List(fileName)).mkString("/"))" class="btn btn-default">Cancel</a> <a href="@helpers.url(repository)/blob/@helpers.encodeRefName((branch :: pathList ::: List(fileName)).mkString("/"))" class="btn btn-default">Cancel</a>
<input type="submit" id="commitButton" class="btn btn-success" value="Commit changes"/> <input type="submit" id="commitButton" class="btn btn-success" value="Commit changes"/>

View File

@@ -100,6 +100,18 @@
<div class="form-group"> <div class="form-group">
<input type="text" id="message" name="message" class="form-control"/> <input type="text" id="message" name="message" class="form-control"/>
</div> </div>
<div class="form-group">
<div class="col-md-12">
<label class="radio">
<input type="radio" id="newBranch" name="newBranch" value="false" checked>
<i class='octicon octicon-git-commit'></i><span>Commit directory to the <strong style="background-color: lightblue; font-family: Consolas">@branch</strong> branch.</span>
</label>
<label class="radio">
<input type="radio" id="newBranch" name="newBranch" value="true">
<i class='octicon octicon-git-pull-request'></i><span>Create a <strong>new branch</strong> for this commit and start a pull request.</span>
</label>
</div>
</div>
<div style="text-align: right;"> <div style="text-align: right;">
@if(fileName.isEmpty){ @if(fileName.isEmpty){
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName((branch :: pathList).mkString("/"))" class="btn btn-default">Cancel</a> <a href="@helpers.url(repository)/tree/@helpers.encodeRefName((branch :: pathList).mkString("/"))" class="btn btn-default">Cancel</a>

View File

@@ -1,7 +1,8 @@
@(branch: String, @(branch: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
pathList: List[String], pathList: List[String],
protectedBranch: Boolean)(implicit context: gitbucket.core.controller.Context) protectedBranch: Boolean,
commit: String)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@import gitbucket.core.util.FileUtil @import gitbucket.core.util.FileUtil
@gitbucket.core.html.main(s"Upload Files at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) { @gitbucket.core.html.main(s"Upload Files at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) {
@@ -37,10 +38,23 @@
<div class="form-group"> <div class="form-group">
<input type="text" name="message" class="form-control"/> <input type="text" name="message" class="form-control"/>
</div> </div>
<div class="form-group">
<div class="col-md-12">
<label class="radio">
<input type="radio" id="newBranch" name="newBranch" value="false" checked>
<i class='octicon octicon-git-commit'></i><span>Commit directory to the <strong style="background-color: lightblue; font-family: Consolas">@branch</strong> branch.</span>
</label>
<label class="radio">
<input type="radio" id="newBranch" name="newBranch" value="true">
<i class='octicon octicon-git-pull-request'></i><span>Create a <strong>new branch</strong> for this commit and start a pull request.</span>
</label>
</div>
</div>
<div style="text-align: right;"> <div style="text-align: right;">
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch + "/" + pathList.mkString("/"))" class="btn btn-default">Cancel</a> <a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch + "/" + pathList.mkString("/"))" class="btn btn-default">Cancel</a>
<input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/> <input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
<input type="hidden" id="upload-files-data" name="uploadFiles" value=""/> <input type="hidden" id="upload-files-data" name="uploadFiles" value=""/>
<input type="hidden" id="commit" name="commit" value="@commit"/>
</div> </div>
</div> </div>
</div> </div>