mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-04 20:45:58 +01:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7bb6b0787 | ||
|
|
ea41786f8c | ||
|
|
962ae2130e | ||
|
|
90ea05f2a1 | ||
|
|
f8bda516d6 | ||
|
|
378c031ecb | ||
|
|
9a5db80dea | ||
|
|
992eb0ceda | ||
|
|
39e1ac2398 | ||
|
|
d1c77de5a0 | ||
|
|
3f8069638c | ||
|
|
d62fc1185c | ||
|
|
768706e1d1 | ||
|
|
8cc9771237 | ||
|
|
8df30ef01b | ||
|
|
dd2e5bfedf | ||
|
|
e3c7eb092f | ||
|
|
5b3c3e2e7c | ||
|
|
0e04925b6b | ||
|
|
9a127256f3 | ||
|
|
1033122fec | ||
|
|
847f96d537 | ||
|
|
70f40846bb | ||
|
|
3a540aa660 | ||
|
|
1adc9b3223 | ||
|
|
0309496df6 | ||
|
|
f83ecac7ae | ||
|
|
cd4d75e35e | ||
|
|
eb61bc50d6 | ||
|
|
4bbb22f73b | ||
|
|
fcb374c5c2 | ||
|
|
a03d1c97c2 | ||
|
|
2d58b7f2d7 | ||
|
|
332a1b4b0b | ||
|
|
6bd58b0c45 | ||
|
|
fb175df851 | ||
|
|
b41aad92f2 | ||
|
|
aabae2ef7f | ||
|
|
0c3d1fd86d | ||
|
|
adba849ec5 | ||
|
|
8539486c6e | ||
|
|
86f4b41beb | ||
|
|
aa54eff3d6 | ||
|
|
27ab21c9a7 | ||
|
|
557ed827d0 | ||
|
|
9cc466a727 | ||
|
|
9a9be12324 | ||
|
|
8e91b9f0b5 | ||
|
|
2862ceb5ad | ||
|
|
d157426d66 | ||
|
|
58635674cb | ||
|
|
f6a048e0f7 | ||
|
|
c4dc1d7334 | ||
|
|
efd5a64749 | ||
|
|
13800a7023 | ||
|
|
43d19d7d52 | ||
|
|
8a8278906a | ||
|
|
d15b3fb2f6 | ||
|
|
bcd92916ca | ||
|
|
810cbda123 | ||
|
|
fee7cebdf1 |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,6 +1,31 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
## 4.20.0 - 23 Dec 2017
|
||||
|
||||
- Squash and rebase merge strategy for pull requests
|
||||
- Quick pull request creation
|
||||
- Download patch from the diff view
|
||||
- Fork and create repository are proceeded asynchronously
|
||||
- Create new repository by copying existing git repository
|
||||
- Hide overflowed repository names in the sidebar
|
||||
- Support CreateEvent web hook
|
||||
- Display conflicting files if pull request can't be merged
|
||||
|
||||
## 4.19.3 - 7 Dec 2017
|
||||
|
||||
- Fix file uploading bug
|
||||
- Fix reply comment form behavior in the diff view
|
||||
|
||||
## 4.19.2 - 3 Dec 2017
|
||||
|
||||
- Fix routing bug in `CompositeScalatraFilter`
|
||||
- Resolve id attribute collision in the web hook editing form
|
||||
|
||||
## 4.19.1 - 2 Dec 2017
|
||||
|
||||
- Update gitbucket-notifications-plugin because it had a version compatibility issue
|
||||
|
||||
## 4.19.0 - 2 Dec 2017
|
||||
- [gitbucket-maven-repository-plugin](https://github.com/takezoe/gitbucket-maven-repository-plugin) is available
|
||||
- Upgrade to Scalatra 2.6
|
||||
|
||||
17
README.md
17
README.md
@@ -68,13 +68,18 @@ Support
|
||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.19.0 - 2 Dec 2017
|
||||
What's New in 4.20.x
|
||||
-------------
|
||||
|
||||
- [gitbucket-maven-repository-plugin](https://github.com/takezoe/gitbucket-maven-repository-plugin) is available
|
||||
- Upgrade to Scalatra 2.6
|
||||
- Improve layout of the system settings page
|
||||
- New extension point (`sshCommandProvider`)
|
||||
- Dropped [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin) from bundled plugins temporary because we couldn't complete update for Scalatra 2.6 before this release.
|
||||
### 4.20.0 - 23 Dec 2017
|
||||
|
||||
- Squash and rebase merge strategy for pull requests
|
||||
- Quick pull request creation
|
||||
- Download patch from the diff view
|
||||
- Fork and create repository are proceeded asynchronously
|
||||
- Create new repository by copying existing git repository
|
||||
- Hide overflowed repository names in the sidebar
|
||||
- Support CreateEvent web hook
|
||||
- Display conflicting files if pull request can't be merged
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
13
build.sbt
13
build.sbt
@@ -3,7 +3,7 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.19.1"
|
||||
val GitBucketVersion = "4.20.0"
|
||||
val ScalatraVersion = "2.6.1"
|
||||
val JettyVersion = "9.4.7.v20170914"
|
||||
|
||||
@@ -26,8 +26,8 @@ resolvers ++= Seq(
|
||||
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||
)
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.9.0.201710071750-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.9.0.201710071750-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.9.1.201712030800-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.9.1.201712030800-r",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||
@@ -43,7 +43,7 @@ libraryDependencies ++= Seq(
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.195",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.1.2",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.2.0",
|
||||
"org.postgresql" % "postgresql" % "42.0.0",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"com.zaxxer" % "HikariCP" % "2.6.1",
|
||||
@@ -160,10 +160,9 @@ executableKey := {
|
||||
IO copyFile(Keys.baseDirectory.value / "plugins.json", pluginsDir / "plugins.json")
|
||||
|
||||
val json = IO read(Keys.baseDirectory.value / "plugins.json")
|
||||
PluginsJson.parse(json).foreach { case (plugin, version, file) =>
|
||||
val url = s"https://github.com/gitbucket/${plugin}/releases/download/${version}/${file}"
|
||||
PluginsJson.getUrls(json).foreach { url =>
|
||||
log info s"Download: ${url}"
|
||||
IO transfer(new java.net.URL(url).openStream, pluginsDir / file)
|
||||
IO transfer(new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
|
||||
}
|
||||
|
||||
// zip it up
|
||||
|
||||
19
plugins.json
19
plugins.json
@@ -7,7 +7,7 @@
|
||||
{
|
||||
"version": "1.4.0",
|
||||
"range": ">=4.19.0",
|
||||
"file": "gitbucket-notifications-plugin_2.12-1.4.0.jar"
|
||||
"url": "https://github.com/gitbucket/gitbucket-notifications-plugin/releases/download/1.4.0/gitbucket-notifications-plugin_2.12-1.4.0.jar"
|
||||
}
|
||||
],
|
||||
"default": true
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
"version": "4.5.0",
|
||||
"range": ">=4.18.0",
|
||||
"file": "gitbucket-emoji-plugin_2.12-4.5.0.jar"
|
||||
"url": "https://github.com/gitbucket/gitbucket-emoji-plugin/releases/download/4.5.0/gitbucket-emoji-plugin_2.12-4.5.0.jar"
|
||||
}
|
||||
],
|
||||
"default": false
|
||||
@@ -33,7 +33,20 @@
|
||||
{
|
||||
"version": "4.11.0",
|
||||
"range": ">=4.19.0",
|
||||
"file": "gitbucket-gist-plugin-assembly-4.11.0.jar"
|
||||
"url": "https://github.com/gitbucket/gitbucket-gist-plugin/releases/download/4.11.0/gitbucket-gist-plugin-assembly-4.11.0.jar"
|
||||
}
|
||||
],
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"id": "pages",
|
||||
"name": "Pages Plugin",
|
||||
"description": "Project pages for gitbucket",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.6.0",
|
||||
"range": ">=4.19.0",
|
||||
"url": "https://github.com/gitbucket/gitbucket-pages-plugin/releases/download/v1.6.0/gitbucket-pages-plugin_2.12-1.6.0.jar"
|
||||
}
|
||||
],
|
||||
"default": false
|
||||
|
||||
@@ -3,17 +3,12 @@ import scala.collection.JavaConverters._
|
||||
|
||||
object PluginsJson {
|
||||
|
||||
def parse(json: String): Seq[(String, String, String)] = {
|
||||
def getUrls(json: String): Seq[String] = {
|
||||
val value = Json.parse(json)
|
||||
value.asArray.values.asScala.map { plugin =>
|
||||
val pluginObject = plugin.asObject
|
||||
val pluginName = "gitbucket-" + pluginObject.get("id").asString + "-plugin"
|
||||
|
||||
val latestVersionObject = pluginObject.get("versions").asArray.asScala.head.asObject
|
||||
val file = latestVersionObject.get("file").asString
|
||||
val version = latestVersionObject.get("version").asString
|
||||
|
||||
(pluginName, version, file)
|
||||
latestVersionObject.get("url").asString
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,5 +45,8 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
new Version("4.17.0"),
|
||||
new Version("4.18.0"),
|
||||
new Version("4.19.0"),
|
||||
new Version("4.19.1")
|
||||
new Version("4.19.1"),
|
||||
new Version("4.19.2"),
|
||||
new Version("4.19.3"),
|
||||
new Version("4.20.0")
|
||||
)
|
||||
|
||||
@@ -35,23 +35,23 @@ case class ApiCommit(
|
||||
|
||||
object ApiCommit{
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo, urlIsHtmlUrl: Boolean = false): ApiCommit = {
|
||||
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
message = commit.fullMessage,
|
||||
timestamp = commit.commitTime,
|
||||
added = diffs._1.collect {
|
||||
added = diffs.collect {
|
||||
case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath
|
||||
},
|
||||
removed = diffs._1.collect {
|
||||
removed = diffs.collect {
|
||||
case x if x.changeType == DiffEntry.ChangeType.DELETE => x.oldPath
|
||||
},
|
||||
modified = diffs._1.collect {
|
||||
modified = diffs.collect {
|
||||
case x if x.changeType != DiffEntry.ChangeType.ADD && x.changeType != DiffEntry.ChangeType.DELETE => x.newPath
|
||||
},
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(repositoryName, urlIsHtmlUrl)
|
||||
}
|
||||
def forPushPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = apply(git, repositoryName, commit, true)
|
||||
def forWebhookPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = apply(git, repositoryName, commit, true)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ object ApiRepository{
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||
this(repositoryInfo.repository, ApiUser(owner))
|
||||
|
||||
def forPushPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
def forWebhookPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount, urlIsHtmlUrl=true)
|
||||
|
||||
def forDummyPayload(owner: ApiUser): ApiRepository =
|
||||
|
||||
@@ -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._
|
||||
@@ -12,7 +12,6 @@ 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._
|
||||
@@ -87,15 +86,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 URL", optionalRequired(_.value("initOption") == "COPY", text())))
|
||||
)(RepositoryCreationForm.apply)
|
||||
|
||||
val forkRepositoryForm = mapping(
|
||||
@@ -461,7 +461,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()
|
||||
@@ -528,11 +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
|
||||
createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.createReadme)
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.created(form.owner, form.name))
|
||||
createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.initOption, form.sourceUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,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()
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
@@ -651,7 +655,7 @@ trait ApiControllerBase extends ControllerBase {
|
||||
JsonFormat(ApiCommits(
|
||||
repositoryName = RepositoryName(repository),
|
||||
commitInfo = commitInfo,
|
||||
diffs = JGitUtil.getDiffs(git, commitInfo.parents.head, commitInfo.id, false, true),
|
||||
diffs = JGitUtil.getDiffs(git, Some(commitInfo.parents.head), commitInfo.id, false, true),
|
||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
||||
|
||||
@@ -116,6 +116,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
|
||||
case Some(file) if(mimeTypeChcker(file.name)) =>
|
||||
defining(FileUtil.generateFileId){ fileId =>
|
||||
f(file, fileId)
|
||||
contentType = "text/plain"
|
||||
Ok(fileId)
|
||||
}
|
||||
case _ => BadRequest()
|
||||
|
||||
@@ -16,6 +16,7 @@ import gitbucket.core.util._
|
||||
import org.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.PersonIdent
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
@@ -50,7 +51,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
)(PullRequestForm.apply)
|
||||
|
||||
val mergeForm = mapping(
|
||||
"message" -> trim(label("Message", text(required)))
|
||||
"message" -> trim(label("Message", text(required))),
|
||||
"strategy" -> trim(label("Strategy", text(required)))
|
||||
)(MergeForm.apply)
|
||||
|
||||
case class PullRequestForm(
|
||||
@@ -69,7 +71,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
labelNames: Option[String]
|
||||
)
|
||||
|
||||
case class MergeForm(message: String)
|
||||
case class MergeForm(message: String, strategy: String)
|
||||
|
||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||
val q = request.getParameter("q")
|
||||
@@ -115,13 +117,13 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||
val hasConflict = LockUtil.lock(s"${owner}/${name}"){
|
||||
val conflictMessage = LockUtil.lock(s"${owner}/${name}"){
|
||||
checkConflict(owner, name, pullreq.branch, issueId)
|
||||
}
|
||||
val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount)
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
|
||||
val mergeStatus = PullRequestService.MergeStatus(
|
||||
hasConflict = hasConflict,
|
||||
conflictMessage = conflictMessage,
|
||||
commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo),
|
||||
branchProtection = branchProtection,
|
||||
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
|
||||
@@ -258,14 +260,30 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
// record activity
|
||||
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
|
||||
|
||||
// merge git repository
|
||||
mergePullRequest(git, pullreq.branch, issueId,
|
||||
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + form.message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
|
||||
val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom,
|
||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
||||
|
||||
val revCommits = using(new RevWalk( git.getRepository )){ revWalk =>
|
||||
commits.flatten.map { commit =>
|
||||
revWalk.parseCommit(git.getRepository.resolve(commit.id))
|
||||
}
|
||||
}.reverse
|
||||
|
||||
// merge git repository
|
||||
form.strategy match {
|
||||
case "merge-commit" =>
|
||||
mergePullRequest(git, pullreq.branch, issueId,
|
||||
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + form.message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
case "rebase" =>
|
||||
rebasePullRequest(git, pullreq.branch, issueId, revCommits,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
case "squash" =>
|
||||
squashPullRequest(git, pullreq.branch, issueId,
|
||||
s"${issue.title} (#${issueId})\n\n" + form.message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
}
|
||||
|
||||
// close issue by content of pull request
|
||||
val defaultBranch = getRepository(owner, name).get.repository.defaultBranch
|
||||
if(pullreq.branch == defaultBranch){
|
||||
@@ -333,7 +351,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
Some(forkedRepository.name)
|
||||
} else if(forkedRepository.repository.originUserName.isEmpty){
|
||||
// when ForkedRepository is the original repository
|
||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
|
||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_.userName == originOwner).map(_.repositoryName)
|
||||
} else if(Some(originOwner) == forkedRepository.repository.originUserName){
|
||||
// Original repository
|
||||
forkedRepository.repository.originRepositoryName
|
||||
@@ -381,9 +399,12 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
commits,
|
||||
diffs,
|
||||
((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
|
||||
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
|
||||
}).filter { case (owner, name) => hasGuestRole(owner, name, context.loginAccount) },
|
||||
case (Some(userName), Some(repositoryName)) => getRepository(userName, repositoryName) match {
|
||||
case Some(x) => x.repository :: getForkedRepositories(userName, repositoryName)
|
||||
case None => getForkedRepositories(userName, repositoryName)
|
||||
}
|
||||
case _ => forkedRepository.repository :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
|
||||
}).map { repository => (repository.userName, repository.repositoryName) },
|
||||
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
|
||||
originId,
|
||||
forkedId,
|
||||
@@ -419,7 +440,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
Some(forkedRepository.name)
|
||||
} else {
|
||||
forkedRepository.repository.originRepositoryName.orElse {
|
||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
|
||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_.userName == originOwner).map(_.repositoryName)
|
||||
}
|
||||
};
|
||||
originRepository <- getRepository(originOwner, originRepositoryName)
|
||||
@@ -434,7 +455,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
checkConflict(originRepository.owner, originRepository.name, originBranch,
|
||||
forkedRepository.owner, forkedRepository.name, forkedBranch)
|
||||
}
|
||||
html.mergecheck(conflict)
|
||||
html.mergecheck(conflict.isDefined)
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
@@ -499,6 +520,35 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/pulls/proposals")(readableUsersOnly { repository =>
|
||||
val branches = JGitUtil.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.filter(x => x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.map(_.name)
|
||||
.reverse
|
||||
|
||||
val targetRepository = (for {
|
||||
parentUserName <- repository.repository.parentUserName
|
||||
parentRepoName <- repository.repository.parentRepositoryName
|
||||
parentRepository <- getRepository(parentUserName, parentRepoName)
|
||||
} yield {
|
||||
parentRepository
|
||||
}).getOrElse {
|
||||
repository
|
||||
}
|
||||
|
||||
val proposedBranches = branches.filter { branch =>
|
||||
getPullRequestsByRequest(repository.owner, repository.name, branch, None).isEmpty
|
||||
}
|
||||
|
||||
html.proposals(proposedBranches, targetRepository, repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* Parses branch identifier and extracts owner and branch name as tuple.
|
||||
*
|
||||
|
||||
@@ -179,7 +179,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
} else {
|
||||
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val lastWeeks = getRecentStatuesContexts(repository.owner, repository.name,
|
||||
Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.of("UTC")))).toSet
|
||||
Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.UTC))).toSet
|
||||
val knownContexts = (lastWeeks ++ protection.status.contexts).toSeq.sortBy(identity)
|
||||
html.branchprotection(repository, branch, protection, knownContexts, flash.get("info"))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -148,14 +149,31 @@ 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")
|
||||
contentType = formats("json")
|
||||
Serialization.write(Map(
|
||||
"creating" -> RepositoryCreationService.isCreating(owner, repository),
|
||||
"error" -> RepositoryCreationService.getCreationError(owner, repository)
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the file list of the specified path and branch.
|
||||
*/
|
||||
@@ -403,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,
|
||||
@@ -418,8 +436,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"prevPath" -> blame.prevPath,
|
||||
"commited" -> blame.commitTime.getTime,
|
||||
"message" -> blame.message,
|
||||
"lines" -> blame.lines)
|
||||
})
|
||||
"lines" -> blame.lines
|
||||
)
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -432,14 +451,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
try {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) { revCommit =>
|
||||
JGitUtil.getDiffs(git, id, true) match {
|
||||
case (diffs, oldCommitId) =>
|
||||
html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
getCommitComments(repository.owner, repository.name, id, true),
|
||||
repository, diffs, oldCommitId, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
}
|
||||
val diffs = JGitUtil.getDiffs(git, None, id, true, false)
|
||||
val oldCommitId = JGitUtil.getParentCommitId(git, id)
|
||||
|
||||
html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
getCommitComments(repository.owner, repository.name, id, true),
|
||||
repository, diffs, oldCommitId, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -447,6 +466,31 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/patch/:id")(referrersOnly { repository =>
|
||||
try {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val diff = JGitUtil.getPatch(git, None, params("id"))
|
||||
contentType = formats("txt")
|
||||
diff
|
||||
}
|
||||
} catch {
|
||||
case e:MissingObjectException => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/patch/*...*")(referrersOnly { repository =>
|
||||
try {
|
||||
val Seq(fromId, toId) = multiParams("splat")
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val diff = JGitUtil.getPatch(git, Some(fromId), toId)
|
||||
contentType = formats("txt")
|
||||
diff
|
||||
}
|
||||
} catch {
|
||||
case e: MissingObjectException => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
val id = params("id")
|
||||
createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content,
|
||||
@@ -614,7 +658,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository.repository.originRepositoryName.getOrElse(repository.name)),
|
||||
getForkedRepositories(
|
||||
repository.repository.originUserName.getOrElse(repository.owner),
|
||||
repository.repository.originRepositoryName.getOrElse(repository.name)),
|
||||
repository.repository.originRepositoryName.getOrElse(repository.name)
|
||||
).map { repository => (repository.userName, repository.repositoryName) },
|
||||
context.loginAccount match {
|
||||
case None => List()
|
||||
case account: Option[Account] => getGroupsByUserName(account.get.userName)
|
||||
|
||||
@@ -76,7 +76,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true, false).filter(_.newPath == pageName + ".md"), repository,
|
||||
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, Some(from), to, true, false).filter(_.newPath == pageName + ".md"), repository,
|
||||
isEditable(repository), flash.get("info"))
|
||||
}
|
||||
})
|
||||
@@ -85,7 +85,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true, false), repository,
|
||||
html.compare(None, from, to, JGitUtil.getDiffs(git, Some(from), to, true, false), repository,
|
||||
isEditable(repository), flash.get("info"))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,7 +35,9 @@ case class PluginMetadata(
|
||||
|
||||
case class VersionDef(
|
||||
version: String,
|
||||
file: String,
|
||||
url: String,
|
||||
range: String
|
||||
)
|
||||
){
|
||||
lazy val file = url.substring(url.lastIndexOf("/") + 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,40 +3,55 @@ package gitbucket.core.service
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
|
||||
import org.eclipse.jgit.merge.MergeStrategy
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
|
||||
import org.eclipse.jgit.api.{Git, MergeResult}
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
||||
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent, Repository}
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.lib.{CommitBuilder, ObjectId, PersonIdent, Repository}
|
||||
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
trait MergeService {
|
||||
import MergeService._
|
||||
|
||||
/**
|
||||
* Checks whether conflict will be caused in merging within pull request.
|
||||
* Returns true if conflict will be caused.
|
||||
*/
|
||||
def checkConflict(userName: String, repositoryName: String, branch: String, issueId: Int): Boolean = {
|
||||
def checkConflict(userName: String, repositoryName: String, branch: String, issueId: Int): Option[String] = {
|
||||
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
MergeCacheInfo(git, branch, issueId).checkConflict()
|
||||
new MergeCacheInfo(git, branch, issueId).checkConflict()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether conflict will be caused in merging within pull request.
|
||||
* only cache check.
|
||||
* Returns Some(true) if conflict will be caused.
|
||||
* Returns None if cache has not created yet.
|
||||
*/
|
||||
def checkConflictCache(userName: String, repositoryName: String, branch: String, issueId: Int): Option[Boolean] = {
|
||||
def checkConflictCache(userName: String, repositoryName: String, branch: String, issueId: Int): Option[Option[String]] = {
|
||||
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
MergeCacheInfo(git, branch, issueId).checkConflictCache()
|
||||
new MergeCacheInfo(git, branch, issueId).checkConflictCache()
|
||||
}
|
||||
}
|
||||
/** merge pull request */
|
||||
def mergePullRequest(git:Git, branch: String, issueId: Int, message:String, committer:PersonIdent): Unit = {
|
||||
MergeCacheInfo(git, branch, issueId).merge(message, committer)
|
||||
|
||||
/** merge the pull request with a merge commit */
|
||||
def mergePullRequest(git: Git, branch: String, issueId: Int, message: String, committer: PersonIdent): Unit = {
|
||||
new MergeCacheInfo(git, branch, issueId).merge(message, committer)
|
||||
}
|
||||
|
||||
/** rebase to the head of the pull request branch */
|
||||
def rebasePullRequest(git: Git, branch: String, issueId: Int, commits: Seq[RevCommit], committer: PersonIdent): Unit = {
|
||||
new MergeCacheInfo(git, branch, issueId).rebase(committer, commits)
|
||||
}
|
||||
|
||||
/** squash commits in the pull request and append it */
|
||||
def squashPullRequest(git: Git, branch: String, issueId: Int, message: String, committer: PersonIdent): Unit = {
|
||||
new MergeCacheInfo(git, branch, issueId).squash(message, committer)
|
||||
}
|
||||
|
||||
/** fetch remote branch to my repository refs/pull/{issueId}/head */
|
||||
def fetchAsPullRequest(userName: String, repositoryName: String, requestUserName: String, requestRepositoryName: String, requestBranch:String, issueId:Int){
|
||||
using(Git.open(getRepositoryDir(userName, repositoryName))){ git =>
|
||||
@@ -46,11 +61,12 @@ trait MergeService {
|
||||
.call
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||
*/
|
||||
def tryMergeRemote(localUserName: String, localRepositoryName: String, localBranch: String,
|
||||
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String): Option[(ObjectId, ObjectId, ObjectId)] = {
|
||||
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String): Either[String, (ObjectId, ObjectId, ObjectId)] = {
|
||||
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
val remoteRefName = s"refs/heads/${remoteBranch}"
|
||||
val tmpRefName = s"refs/remote-temp/${remoteUserName}/${remoteRepositoryName}/${remoteBranch}"
|
||||
@@ -67,12 +83,12 @@ trait MergeService {
|
||||
val mergeTip = git.getRepository.resolve(tmpRefName)
|
||||
try {
|
||||
if(merger.merge(mergeBaseTip, mergeTip)){
|
||||
Some((merger.getResultTreeId, mergeBaseTip, mergeTip))
|
||||
Right((merger.getResultTreeId, mergeBaseTip, mergeTip))
|
||||
} else {
|
||||
None
|
||||
Left(createConflictMessage(mergeTip, mergeBaseTip, merger))
|
||||
}
|
||||
} catch {
|
||||
case e: NoMergeBaseException => None
|
||||
case e: NoMergeBaseException => Left(e.toString)
|
||||
}
|
||||
} finally {
|
||||
val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
|
||||
@@ -81,30 +97,33 @@ trait MergeService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||
* Checks whether conflict will be caused in merging. Returns `Some(errorMessage)` if conflict will be caused.
|
||||
*/
|
||||
def checkConflict(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean =
|
||||
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).isEmpty
|
||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Option[String] =
|
||||
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption
|
||||
|
||||
def pullRemote(localUserName: String, localRepositoryName: String, localBranch: String,
|
||||
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String,
|
||||
loginAccount: Account, message: String): Option[ObjectId] = {
|
||||
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map{ case (newTreeId, oldBaseId, oldHeadId) =>
|
||||
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map { case (newTreeId, oldBaseId, oldHeadId) =>
|
||||
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
val newCommit = Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId))
|
||||
Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge"))
|
||||
}
|
||||
oldBaseId
|
||||
}
|
||||
}.toOption
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object MergeService{
|
||||
|
||||
object Util{
|
||||
// return treeId
|
||||
// return merge commit id
|
||||
def createMergeCommit(repository: Repository, treeId: ObjectId, committer: PersonIdent, message: String, parents: Seq[ObjectId]): ObjectId = {
|
||||
val mergeCommit = new CommitBuilder()
|
||||
mergeCommit.setTreeId(treeId)
|
||||
@@ -113,14 +132,14 @@ object MergeService{
|
||||
mergeCommit.setCommitter(committer)
|
||||
mergeCommit.setMessage(message)
|
||||
// insertObject and got mergeCommit Object Id
|
||||
val inserter = repository.newObjectInserter
|
||||
val mergeCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
inserter.close()
|
||||
mergeCommitId
|
||||
using(repository.newObjectInserter){ inserter =>
|
||||
val mergeCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
mergeCommitId
|
||||
}
|
||||
}
|
||||
def updateRefs(repository: Repository, ref: String, newObjectId: ObjectId, force: Boolean, committer: PersonIdent, refLogMessage: Option[String] = None):Unit = {
|
||||
// update refs
|
||||
|
||||
def updateRefs(repository: Repository, ref: String, newObjectId: ObjectId, force: Boolean, committer: PersonIdent, refLogMessage: Option[String] = None): Unit = {
|
||||
val refUpdate = repository.updateRef(ref)
|
||||
refUpdate.setNewObjectId(newObjectId)
|
||||
refUpdate.setForceUpdate(force)
|
||||
@@ -129,33 +148,41 @@ object MergeService{
|
||||
refUpdate.update()
|
||||
}
|
||||
}
|
||||
case class MergeCacheInfo(git:Git, branch:String, issueId:Int){
|
||||
val repository = git.getRepository
|
||||
val mergedBranchName = s"refs/pull/${issueId}/merge"
|
||||
val conflictedBranchName = s"refs/pull/${issueId}/conflict"
|
||||
|
||||
class MergeCacheInfo(git: Git, branch: String, issueId: Int){
|
||||
|
||||
private val repository = git.getRepository
|
||||
|
||||
private val mergedBranchName = s"refs/pull/${issueId}/merge"
|
||||
private val conflictedBranchName = s"refs/pull/${issueId}/conflict"
|
||||
|
||||
lazy val mergeBaseTip = repository.resolve(s"refs/heads/${branch}")
|
||||
lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head")
|
||||
def checkConflictCache(): Option[Boolean] = {
|
||||
Option(repository.resolve(mergedBranchName)).flatMap{ merged =>
|
||||
lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head")
|
||||
|
||||
def checkConflictCache(): Option[Option[String]] = {
|
||||
Option(repository.resolve(mergedBranchName)).flatMap { merged =>
|
||||
if(parseCommit(merged).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||
// merged branch exists
|
||||
Some(false)
|
||||
Some(None)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}.orElse(Option(repository.resolve(conflictedBranchName)).flatMap{ conflicted =>
|
||||
if(parseCommit(conflicted).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||
val commit = parseCommit(conflicted)
|
||||
if(commit.getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||
// conflict branch exists
|
||||
Some(true)
|
||||
Some(Some(commit.getFullMessage))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
def checkConflict():Boolean ={
|
||||
|
||||
def checkConflict(): Option[String] ={
|
||||
checkConflictCache.getOrElse(checkConflictForce)
|
||||
}
|
||||
def checkConflictForce():Boolean ={
|
||||
|
||||
def checkConflictForce(): Option[String] ={
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(repository, true)
|
||||
val conflicted = try {
|
||||
!merger.merge(mergeBaseTip, mergeTip)
|
||||
@@ -164,35 +191,113 @@ object MergeService{
|
||||
}
|
||||
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
|
||||
val committer = mergeTipCommit.getCommitterIdent
|
||||
def updateBranch(treeId:ObjectId, message:String, branchName:String){
|
||||
|
||||
def _updateBranch(treeId: ObjectId, message: String, branchName: String){
|
||||
// creates merge commit
|
||||
val mergeCommitId = createMergeCommit(treeId, committer, message)
|
||||
Util.updateRefs(repository, branchName, mergeCommitId, true, committer)
|
||||
}
|
||||
|
||||
if(!conflicted){
|
||||
updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName)
|
||||
_updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName)
|
||||
git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call()
|
||||
None
|
||||
} else {
|
||||
updateBranch(mergeTipCommit.getTree().getId(), s"can't merge ${mergeTip.name} into ${mergeBaseTip.name}", conflictedBranchName)
|
||||
val message = createConflictMessage(mergeTip, mergeBaseTip, merger)
|
||||
_updateBranch(mergeTipCommit.getTree().getId(), message, conflictedBranchName)
|
||||
git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call()
|
||||
Some(message)
|
||||
}
|
||||
conflicted
|
||||
}
|
||||
|
||||
// update branch from cache
|
||||
def merge(message:String, committer:PersonIdent) = {
|
||||
if(checkConflict()){
|
||||
if(checkConflict().isDefined){
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
val mergeResultCommit = parseCommit( Option(repository.resolve(mergedBranchName)).getOrElse(throw new RuntimeException(s"not found branch ${mergedBranchName}")) )
|
||||
val mergeResultCommit = parseCommit(Option(repository.resolve(mergedBranchName)).getOrElse {
|
||||
throw new RuntimeException(s"Not found branch ${mergedBranchName}")
|
||||
})
|
||||
// creates merge commit
|
||||
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
|
||||
// update refs
|
||||
Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
|
||||
}
|
||||
|
||||
def rebase(committer: PersonIdent, commits: Seq[RevCommit]): Unit = {
|
||||
if(checkConflict().isDefined){
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
|
||||
def _cloneCommit(commit: RevCommit, parents: Array[ObjectId]): CommitBuilder = {
|
||||
val newCommit = new CommitBuilder()
|
||||
newCommit.setTreeId(commit.getTree.getId)
|
||||
parents.foreach { parentId =>
|
||||
newCommit.addParentId(parentId)
|
||||
}
|
||||
newCommit.setAuthor(commit.getAuthorIdent)
|
||||
newCommit.setCommitter(committer)
|
||||
newCommit.setMessage(commit.getFullMessage)
|
||||
newCommit
|
||||
}
|
||||
|
||||
val mergeBaseTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeBaseTip ))
|
||||
var previousId = mergeBaseTipCommit.getId
|
||||
|
||||
using(repository.newObjectInserter){ inserter =>
|
||||
commits.foreach { commit =>
|
||||
val nextCommit = _cloneCommit(commit, Array(previousId))
|
||||
previousId = inserter.insert(nextCommit)
|
||||
}
|
||||
inserter.flush()
|
||||
}
|
||||
|
||||
Util.updateRefs(repository, s"refs/heads/${branch}", previousId, false, committer, Some("rebased"))
|
||||
}
|
||||
|
||||
def squash(message: String, committer: PersonIdent): Unit = {
|
||||
if(checkConflict().isDefined){
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
|
||||
val mergeBaseTipCommit = using(new RevWalk( repository ))(_.parseCommit(mergeBaseTip))
|
||||
val mergeBranchHeadCommit = using(new RevWalk( repository ))(_.parseCommit(repository.resolve(mergedBranchName)))
|
||||
|
||||
// Create squash commit
|
||||
val mergeCommit = new CommitBuilder()
|
||||
mergeCommit.setTreeId(mergeBranchHeadCommit.getTree.getId)
|
||||
mergeCommit.setParentId(mergeBaseTipCommit)
|
||||
mergeCommit.setAuthor(mergeBranchHeadCommit.getAuthorIdent)
|
||||
mergeCommit.setCommitter(committer)
|
||||
mergeCommit.setMessage(message)
|
||||
|
||||
// insertObject and got squash commit Object Id
|
||||
val newCommitId = using(repository.newObjectInserter){ inserter =>
|
||||
val newCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
newCommitId
|
||||
}
|
||||
|
||||
Util.updateRefs(repository, mergedBranchName, newCommitId, true, committer)
|
||||
|
||||
// rebase to squash commit
|
||||
Util.updateRefs(repository, s"refs/heads/${branch}", repository.resolve(mergedBranchName), false, committer, Some("squashed"))
|
||||
}
|
||||
|
||||
// return treeId
|
||||
private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) =
|
||||
Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip))
|
||||
|
||||
private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id))
|
||||
private def parseCommit(id: ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id))
|
||||
|
||||
}
|
||||
|
||||
private def createConflictMessage(mergeTip: ObjectId, mergeBaseTip: ObjectId, merger: Merger): String = {
|
||||
val mergeResults = merger.asInstanceOf[RecursiveMerger].getMergeResults
|
||||
|
||||
s"Can't merge ${mergeTip.name} into ${mergeBaseTip.name}\n\n" +
|
||||
"Conflicting files:\n" +
|
||||
mergeResults.asScala.map { case (key, _) => "- " + key + "\n" }.mkString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
||||
commitIdFrom,
|
||||
commitIdTo)
|
||||
|
||||
def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Boolean)
|
||||
def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Option[Boolean])
|
||||
(implicit s: Session): List[PullRequest] =
|
||||
PullRequests
|
||||
.join(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
@@ -87,7 +87,7 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
||||
(t1.requestUserName === userName.bind) &&
|
||||
(t1.requestRepositoryName === repositoryName.bind) &&
|
||||
(t1.requestBranch === branch.bind) &&
|
||||
(t2.closed === closed.bind)
|
||||
(t2.closed === closed.get.bind, closed.isDefined)
|
||||
}
|
||||
.map { case (t1, t2) => t1 }
|
||||
.list
|
||||
@@ -118,7 +118,7 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
|
||||
*/
|
||||
def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit =
|
||||
getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq =>
|
||||
getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq =>
|
||||
if(Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run){
|
||||
// Update the git repository
|
||||
val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest(
|
||||
@@ -230,7 +230,7 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
||||
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}
|
||||
|
||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true, false)
|
||||
val diffs = JGitUtil.getDiffs(newGit, Some(oldId.getName), newId.getName, true, false)
|
||||
|
||||
(commits, diffs)
|
||||
}
|
||||
@@ -244,8 +244,8 @@ object PullRequestService {
|
||||
case class PullRequestCount(userName: String, count: Int)
|
||||
|
||||
case class MergeStatus(
|
||||
hasConflict: Boolean,
|
||||
commitStatues:List[CommitStatus],
|
||||
conflictMessage: Option[String],
|
||||
commitStatues: List[CommitStatus],
|
||||
branchProtection: ProtectedBranchService.ProtectedBranchInfo,
|
||||
branchIsOutOfDate: Boolean,
|
||||
hasUpdatePermission: Boolean,
|
||||
@@ -253,12 +253,13 @@ object PullRequestService {
|
||||
hasMergePermission: Boolean,
|
||||
commitIdTo: String){
|
||||
|
||||
val hasConflict = conflictMessage.isDefined
|
||||
val statuses: List[CommitStatus] =
|
||||
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
|
||||
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS))
|
||||
val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
lazy val commitStateSummary:(CommitState, String) = {
|
||||
val stateMap = statuses.groupBy(_.state)
|
||||
val state = CommitState.combine(stateMap.keySet)
|
||||
|
||||
@@ -1,71 +1,206 @@
|
||||
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.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
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
||||
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, Option[String]]()
|
||||
|
||||
def isCreating(owner: String, repository: String): Boolean = {
|
||||
Option(Creating.get(s"${owner}/${repository}")).map(_.isEmpty).getOrElse(false)
|
||||
}
|
||||
|
||||
def startCreation(owner: String, repository: String): Unit = {
|
||||
Creating.put(s"${owner}/${repository}", None)
|
||||
}
|
||||
|
||||
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.remove(s"${owner}/${repository}")).getOrElse(None)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
val ownerAccount = getAccountByUserName(owner).get
|
||||
val loginUserName = loginAccount.userName
|
||||
def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String],
|
||||
isPrivate: Boolean, createReadme: Boolean): Future[Unit] = {
|
||||
createRepository(loginAccount, owner, name, description, isPrivate, if (createReadme) "README" else "EMPTY", None)
|
||||
}
|
||||
|
||||
// Insert to the database at first
|
||||
insertRepository(name, owner, description, isPrivate)
|
||||
def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String],
|
||||
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
|
||||
|
||||
// // Add collaborators for group repository
|
||||
// if(ownerAccount.isGroupAccount){
|
||||
// getGroupMembers(owner).foreach { member =>
|
||||
// addCollaborator(owner, name, member.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 default labels
|
||||
insertDefaultLabels(owner, name)
|
||||
|
||||
// Insert default priorities
|
||||
insertDefaultPriorities(owner, name)
|
||||
// Insert to the database at first
|
||||
insertRepository(name, owner, description, isPrivate)
|
||||
|
||||
// Create the actual repository
|
||||
val gitdir = getRepositoryDir(owner, name)
|
||||
JGitUtil.initRepository(gitdir)
|
||||
// // Add collaborators for group repository
|
||||
// if(ownerAccount.isGroupAccount){
|
||||
// getGroupMembers(owner).foreach { member =>
|
||||
// addCollaborator(owner, name, member.userName)
|
||||
// }
|
||||
// }
|
||||
|
||||
if(createReadme){
|
||||
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"
|
||||
// Insert default labels
|
||||
insertDefaultLabels(owner, name)
|
||||
|
||||
// Insert default priorities
|
||||
insertDefaultPriorities(owner, name)
|
||||
|
||||
// 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"
|
||||
}
|
||||
|
||||
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()
|
||||
copyRepositoryDir.foreach { dir =>
|
||||
try {
|
||||
using(Git.open(dir)) { git =>
|
||||
git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call()
|
||||
}
|
||||
} finally {
|
||||
FileUtils.deleteQuietly(dir)
|
||||
}
|
||||
}
|
||||
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
||||
// Create Wiki repository
|
||||
createWikiRepository(loginAccount, owner, name)
|
||||
|
||||
// Record activity
|
||||
recordCreateRepositoryActivity(owner, name, loginUserName)
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.created(owner, name))
|
||||
}
|
||||
|
||||
RepositoryCreationService.endCreation(owner, name, None)
|
||||
|
||||
} catch {
|
||||
case ex: Exception => RepositoryCreationService.endCreation(owner, name, Some(ex.toString))
|
||||
}
|
||||
}
|
||||
|
||||
// Create Wiki repository
|
||||
createWikiRepository(loginAccount, owner, name)
|
||||
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)
|
||||
|
||||
// Record activity
|
||||
recordCreateRepositoryActivity(owner, name, loginUserName)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
def insertDefaultLabels(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
||||
|
||||
@@ -95,9 +95,9 @@ trait RepositoryService { self: AccountService =>
|
||||
|
||||
RepositoryWebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
RepositoryWebHookEvents.insertAll(webHookEvents .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Priorities .insertAll(priorities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Priorities .insertAll(priorities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
|
||||
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||
val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||
@@ -159,21 +159,21 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
|
||||
def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
||||
Activities .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Collaborators .filter(_.byRepository(userName, repositoryName)).delete
|
||||
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueLabels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Labels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
|
||||
PullRequests .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Issues .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Priorities .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueId .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Activities .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Collaborators .filter(_.byRepository(userName, repositoryName)).delete
|
||||
CommitComments .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueLabels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Labels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
|
||||
PullRequests .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Issues .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Priorities .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueId .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
||||
RepositoryWebHooks .filter(_.byRepository(userName, repositoryName)).delete
|
||||
RepositoryWebHookEvents .filter(_.byRepository(userName, repositoryName)).delete
|
||||
DeployKeys .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Repositories .filter(_.byRepository(userName, repositoryName)).delete
|
||||
DeployKeys .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Repositories .filter(_.byRepository(userName, repositoryName)).delete
|
||||
|
||||
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME
|
||||
Repositories
|
||||
@@ -390,7 +390,7 @@ trait RepositoryService { self: AccountService =>
|
||||
Collaborators
|
||||
.join(Accounts).on(_.collaboratorName === _.userName)
|
||||
.filter { case (t1, t2) => t1.byRepository(userName, repositoryName) }
|
||||
.map { case (t1, t2) => (t1, t2.groupAccount) }
|
||||
.map { case (t1, t2) => (t1, t2.groupAccount) }
|
||||
.sortBy { case (t1, t2) => t1.collaboratorName }
|
||||
.list
|
||||
|
||||
@@ -402,13 +402,13 @@ trait RepositoryService { self: AccountService =>
|
||||
val q1 = Collaborators
|
||||
.join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) }
|
||||
.filter { case (t1, t2) => t1.byRepository(userName, repositoryName) }
|
||||
.map { case (t1, t2) => (t1.collaboratorName, t1.role) }
|
||||
.map { case (t1, t2) => (t1.collaboratorName, t1.role) }
|
||||
|
||||
val q2 = Collaborators
|
||||
.join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) }
|
||||
.join(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName }
|
||||
.filter { case ((t1, t2), t3) => t1.byRepository(userName, repositoryName) }
|
||||
.map { case ((t1, t2), t3) => (t3.userName, t1.role) }
|
||||
.map { case ((t1, t2), t3) => (t3.userName, t1.role) }
|
||||
|
||||
q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1)
|
||||
}
|
||||
@@ -443,17 +443,31 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
}
|
||||
|
||||
def isReadable(repository: Repository, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
if(!repository.isPrivate){
|
||||
true
|
||||
} else {
|
||||
loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => true
|
||||
case Some(x) if(repository.userName == x.userName) => true
|
||||
case Some(x) if(getGroupMembers(repository.userName).exists(_.userName == x.userName)) => true
|
||||
case Some(x) if(getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
|
||||
Query(Repositories.filter { t =>
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
}.length).first
|
||||
|
||||
|
||||
def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[(String, String)] =
|
||||
def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[Repository] =
|
||||
Repositories.filter { t =>
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
}
|
||||
.sortBy(_.userName asc).map(t => t.userName -> t.repositoryName).list
|
||||
.sortBy(_.userName asc).list//.map(t => t.userName -> t.repositoryName).list
|
||||
|
||||
private val templateExtensions = Seq("md", "markdown")
|
||||
|
||||
|
||||
@@ -362,6 +362,35 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
object WebHookService {
|
||||
trait WebHookPayload
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#createevent
|
||||
case class WebHookCreatePayload(
|
||||
sender: ApiUser,
|
||||
description: String,
|
||||
ref: String,
|
||||
ref_type: String,
|
||||
master_branch: String,
|
||||
repository: ApiRepository
|
||||
) extends FieldSerializable with WebHookPayload {
|
||||
val pusher_type = "user"
|
||||
}
|
||||
|
||||
object WebHookCreatePayload {
|
||||
|
||||
def apply(git: Git, sender: Account, refName: String, repositoryInfo: RepositoryInfo,
|
||||
commits: List[CommitInfo], repositoryOwner: Account,
|
||||
ref: String, refType: String): WebHookCreatePayload =
|
||||
WebHookCreatePayload(
|
||||
sender = ApiUser(sender),
|
||||
ref = ref,
|
||||
ref_type = refType,
|
||||
description = repositoryInfo.repository.description.getOrElse(""),
|
||||
master_branch = repositoryInfo.repository.defaultBranch,
|
||||
repository = ApiRepository.forWebhookPayload(
|
||||
repositoryInfo,
|
||||
owner= ApiUser(repositoryOwner))
|
||||
)
|
||||
}
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#pushevent
|
||||
case class WebHookPushPayload(
|
||||
pusher: ApiPusher,
|
||||
@@ -391,8 +420,8 @@ object WebHookService {
|
||||
ref = refName,
|
||||
before = ObjectId.toString(oldId),
|
||||
after = ObjectId.toString(newId),
|
||||
commits = commits.map{ commit => ApiCommit.forPushPayload(git, RepositoryName(repositoryInfo), commit) },
|
||||
repository = ApiRepository.forPushPayload(
|
||||
commits = commits.map{ commit => ApiCommit.forWebhookPayload(git, RepositoryName(repositoryInfo), commit) },
|
||||
repository = ApiRepository.forWebhookPayload(
|
||||
repositoryInfo,
|
||||
owner= ApiUser(repositoryOwner))
|
||||
)
|
||||
|
||||
@@ -28,20 +28,30 @@ class CompositeScalatraFilter extends Filter {
|
||||
}
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI
|
||||
val contextPath = request.getServletContext.getContextPath
|
||||
val requestPath = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length)
|
||||
val checkPath = if(requestPath.endsWith("/")){
|
||||
requestPath
|
||||
} else {
|
||||
requestPath + "/"
|
||||
}
|
||||
|
||||
filters
|
||||
.filter { case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
(requestUri + "/").startsWith(start)
|
||||
}
|
||||
.foreach { case (filter, _) =>
|
||||
val mockChain = new MockFilterChain()
|
||||
filter.doFilter(request, response, mockChain)
|
||||
if(mockChain.continue == false){
|
||||
return ()
|
||||
if(!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") &&
|
||||
!checkPath.startsWith("/plugin-assets/") && !checkPath.startsWith("/console/")){
|
||||
filters
|
||||
.filter { case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
checkPath.startsWith(start)
|
||||
}
|
||||
}
|
||||
.foreach { case (filter, _) =>
|
||||
val mockChain = new MockFilterChain()
|
||||
filter.doFilter(request, response, mockChain)
|
||||
if(mockChain.continue == false){
|
||||
return ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
@@ -56,8 +66,8 @@ class MockFilterChain extends FilterChain {
|
||||
}
|
||||
}
|
||||
|
||||
class FilterChainFilter(chain: FilterChain) extends Filter {
|
||||
override def init(filterConfig: FilterConfig): Unit = ()
|
||||
override def destroy(): Unit = ()
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, mockChain: FilterChain) = chain.doFilter(request, response)
|
||||
}
|
||||
//class FilterChainFilter(chain: FilterChain) extends Filter {
|
||||
// override def init(filterConfig: FilterConfig): Unit = ()
|
||||
// override def destroy(): Unit = ()
|
||||
// override def doFilter(request: ServletRequest, response: ServletResponse, mockChain: FilterChain) = chain.doFilter(request, response)
|
||||
//}
|
||||
|
||||
@@ -306,6 +306,18 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
newId = command.getNewId(), oldId = command.getOldId())
|
||||
}
|
||||
}
|
||||
if (command.getType == ReceiveCommand.Type.CREATE) {
|
||||
callWebHookOf(owner, repository, WebHook.Create) {
|
||||
for {
|
||||
pusherAccount <- getAccountByUserName(pusher)
|
||||
ownerAccount <- getAccountByUserName(owner)
|
||||
} yield {
|
||||
val refType = if (refName(1) == "tags") "tag" else "branch"
|
||||
WebHookCreatePayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount,
|
||||
ref = branchName, refType = refType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach(_.postReceive(owner, repository, receivePack, command, pusher))
|
||||
@@ -347,8 +359,8 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
commitIds.map { case (oldCommitId, newCommitId) =>
|
||||
val commits = using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
JGitUtil.getCommitLog(git, oldCommitId, newCommitId).flatMap { commit =>
|
||||
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||
diffs._1.collect { case diff if diff.newPath.toLowerCase.endsWith(".md") =>
|
||||
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
|
||||
diffs.collect { case diff if diff.newPath.toLowerCase.endsWith(".md") =>
|
||||
val action = if(diff.changeType == ChangeType.ADD) "created" else "edited"
|
||||
val fileName = diff.newPath
|
||||
(action, fileName, commit.id)
|
||||
|
||||
@@ -97,16 +97,10 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService with A
|
||||
{
|
||||
defining(request.paths){ paths =>
|
||||
getRepository(paths(0), paths(1)).map { repository =>
|
||||
if(!repository.repository.isPrivate){
|
||||
if(isReadable(repository.repository, context.loginAccount)){
|
||||
action(repository)
|
||||
} else {
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ import java.util.function.Consumer
|
||||
|
||||
import org.cache2k.{Cache2kBuilder, CacheEntry}
|
||||
import org.eclipse.jgit.api.errors.{InvalidRefNameException, JGitInternalException, NoHeadException, RefAlreadyExistsException}
|
||||
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter}
|
||||
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator}
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
import org.eclipse.jgit.util.io.DisabledOutputStream
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
@@ -518,93 +519,49 @@ object JGitUtil {
|
||||
}.toMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tuple of diff of the given commit and parent commit ids.
|
||||
* DiffInfos returned from this method don't include the patch property.
|
||||
*/
|
||||
def getDiffs(git: Git, id: String, fetchContent: Boolean): (List[DiffInfo], Option[String]) = {
|
||||
@scala.annotation.tailrec
|
||||
def getCommitLog(i: java.util.Iterator[RevCommit], logs: List[RevCommit]): List[RevCommit] =
|
||||
i.hasNext match {
|
||||
case true if(logs.size < 2) => getCommitLog(i, logs :+ i.next)
|
||||
case _ => logs
|
||||
}
|
||||
def getPatch(git: Git, from: Option[String], to: String): String = {
|
||||
val out = new ByteArrayOutputStream()
|
||||
val df = new DiffFormatter(out)
|
||||
df.setRepository(git.getRepository)
|
||||
df.setDiffComparator(RawTextComparator.DEFAULT)
|
||||
df.setDetectRenames(true)
|
||||
df.format(getDiffEntries(git, from, to).head)
|
||||
new String(out.toByteArray, "UTF-8")
|
||||
}
|
||||
|
||||
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(id)))
|
||||
val commits = getCommitLog(revWalk.iterator, Nil)
|
||||
val revCommit = commits(0)
|
||||
val df = new DiffFormatter(DisabledOutputStream.INSTANCE)
|
||||
df.setRepository(git.getRepository)
|
||||
|
||||
if(commits.length >= 2){
|
||||
// not initial commit
|
||||
val oldCommit = if(revCommit.getParentCount >= 2) {
|
||||
// merge commit
|
||||
revCommit.getParents.head
|
||||
} else {
|
||||
commits(1)
|
||||
}
|
||||
(getDiffs(git, oldCommit.getName, id, fetchContent, false), Some(oldCommit.getName))
|
||||
|
||||
} else {
|
||||
// initial commit
|
||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
treeWalk.setRecursive(true)
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
|
||||
while(treeWalk.next){
|
||||
val newIsImage = FileUtil.isImage(treeWalk.getPathString)
|
||||
buffer.append((if(!fetchContent){
|
||||
DiffInfo(
|
||||
changeType = ChangeType.ADD,
|
||||
oldPath = "",
|
||||
newPath = treeWalk.getPathString,
|
||||
oldContent = None,
|
||||
newContent = None,
|
||||
oldIsImage = false,
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = None,
|
||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||
oldMode = treeWalk.getFileMode(0).toString,
|
||||
newMode = treeWalk.getFileMode(0).toString,
|
||||
tooLarge = false,
|
||||
patch = None
|
||||
)
|
||||
} else {
|
||||
DiffInfo(
|
||||
changeType = ChangeType.ADD,
|
||||
oldPath = "",
|
||||
newPath = treeWalk.getPathString,
|
||||
oldContent = None,
|
||||
newContent = JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
oldIsImage = false,
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = None,
|
||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||
oldMode = treeWalk.getFileMode(0).toString,
|
||||
newMode = treeWalk.getFileMode(0).toString,
|
||||
tooLarge = false,
|
||||
patch = None
|
||||
)
|
||||
}))
|
||||
val toCommit = revWalk.parseCommit(git.getRepository.resolve(to))
|
||||
from match {
|
||||
case None => {
|
||||
toCommit.getParentCount match {
|
||||
case 0 => df.scan(new EmptyTreeIterator(), new CanonicalTreeParser(null, git.getRepository.newObjectReader(), toCommit.getTree)).asScala
|
||||
case _ => df.scan(toCommit.getParent(0), toCommit.getTree).asScala
|
||||
}
|
||||
(buffer.toList, None)
|
||||
}
|
||||
case Some(from) => {
|
||||
val fromCommit = revWalk.parseCommit(git.getRepository.resolve(from))
|
||||
df.scan(fromCommit.getTree, toCommit.getTree).asScala
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getDiffs(git: Git, from: String, to: String, fetchContent: Boolean, makePatch: Boolean): List[DiffInfo] = {
|
||||
val reader = git.getRepository.newObjectReader
|
||||
val oldTreeIter = new CanonicalTreeParser
|
||||
oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}"))
|
||||
def getParentCommitId(git: Git, id: String): Option[String] = {
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
val commit = revWalk.parseCommit(git.getRepository.resolve(id))
|
||||
commit.getParentCount match {
|
||||
case 0 => None
|
||||
case _ => Some(commit.getParent(0).getName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val newTreeIter = new CanonicalTreeParser
|
||||
newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}"))
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
git.getRepository.getConfig.setString("diff", null, "renames", "copies")
|
||||
|
||||
val diffs = git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala
|
||||
def getDiffs(git: Git, from: Option[String], to: String, fetchContent: Boolean, makePatch: Boolean): List[DiffInfo] = {
|
||||
val diffs = getDiffEntries(git, from, to)
|
||||
diffs.map { diff =>
|
||||
if(diffs.size > 100){
|
||||
DiffInfo(
|
||||
@@ -639,7 +596,7 @@ object JGitUtil {
|
||||
oldMode = diff.getOldMode.toString,
|
||||
newMode = diff.getNewMode.toString,
|
||||
tooLarge = false,
|
||||
patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None)
|
||||
patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None) // TODO use DiffFormatter
|
||||
)
|
||||
} else {
|
||||
DiffInfo(
|
||||
@@ -655,7 +612,7 @@ object JGitUtil {
|
||||
oldMode = diff.getOldMode.toString,
|
||||
newMode = diff.getNewMode.toString,
|
||||
tooLarge = false,
|
||||
patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None)
|
||||
patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None) // TODO use DiffFormatter
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1024,7 +981,7 @@ object JGitUtil {
|
||||
val blame = blamer.call()
|
||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||
var idLine = List[(String, Int)]()
|
||||
val commits = 0.to(blame.getResultContents().size() - 1).map{ i =>
|
||||
val commits = 0.to(blame.getResultContents().size() - 1).map { i =>
|
||||
val c = blame.getSourceCommit(i)
|
||||
if(!blameMap.contains(c.name)){
|
||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||
|
||||
@@ -39,7 +39,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="description" class="strong">Description (optional):</label>
|
||||
<input type="text" name="description" id="description" class="form-control" style="width: 95%;"/>
|
||||
<input type="text" name="description" id="description" class="form-control" />
|
||||
</fieldset>
|
||||
<fieldset class="border-top">
|
||||
<label class="radio">
|
||||
@@ -58,14 +58,30 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset class="border-top">
|
||||
<label for="createReadme" class="checkbox">
|
||||
<input type="checkbox" name="createReadme" id="createReadme"/>
|
||||
<label class="radio">
|
||||
<input type="radio" name="initOption" value="EMPTY" checked/>
|
||||
<span class="strong">Create an empty repository</span>
|
||||
<div class="normal muted">
|
||||
Create an empty repository. You have to initialize by yourself initially.
|
||||
</div>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="initOption" value="README"/>
|
||||
<span class="strong">Initialize this repository with a README</span>
|
||||
<div class="normal muted">
|
||||
This will let you immediately clone the repository to your computer. Skip this step if you’re importing an existing repository.
|
||||
Create a repository which has README.md. You can clone the repository immediately.
|
||||
</div>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="initOption" value="COPY"/>
|
||||
<span class="strong">Copy existing git repository</span>
|
||||
<div class="normal muted">
|
||||
Create new repository from existing git repository.
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
<input type="text" class="form-control" name="sourceUrl" id="sourceUrl" disabled placeholder="Source git repository URL..."/>
|
||||
<span id="error-sourceUrl" class="error"></span>
|
||||
<fieldset class="border-top form-actions">
|
||||
<input type="submit" class="btn btn-success" value="Create repository"/>
|
||||
</fieldset>
|
||||
@@ -83,4 +99,8 @@ $('#owner-dropdown a').click(function(){
|
||||
|
||||
$('#owner-dropdown span.strong').html($(this).find('span').html());
|
||||
});
|
||||
|
||||
$('input[name=initOption]').click(function () {
|
||||
$('#sourceUrl').prop('disabled', $('input[name=initOption]:checked').val() != 'COPY');
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@if(hasWritePermission) {
|
||||
<li id="create-branch" style="display: none;">
|
||||
<a><form action="@helpers.url(repository)/branches" method="post" style="margin: 0;">
|
||||
<span class="new-branch-name">Create branch: <span class="new-branch"></span></span>
|
||||
<span class="strong">Create branch: <span class="new-branch"></span></span>
|
||||
<br><span style="padding-left: 17px;">from '@branch'</span>
|
||||
<input type="hidden" name="new">
|
||||
<input type="hidden" name="from" value="@branch">
|
||||
|
||||
@@ -10,9 +10,15 @@
|
||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
@if(showIndex){
|
||||
<div class="pull-right" style="margin-bottom: 10px;">
|
||||
@if(oldCommitId.isEmpty && newCommitId.isDefined) {
|
||||
<a href="@helpers.url(repository)/patch/@newCommitId" class="btn btn-default">Patch</a>
|
||||
}
|
||||
@if(oldCommitId.isDefined && newCommitId.isDefined) {
|
||||
<a href="@helpers.url(repository)/patch/@oldCommitId...@newCommitId" class="btn btn-default">Patch</a>
|
||||
}
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<input type="button" id="btn-unified" class="btn btn-default btn-small active" value="Unified">
|
||||
<input type="button" id="btn-split" class="btn btn-default btn-small" value="Split">
|
||||
<input type="button" id="btn-unified" class="btn btn-default active" value="Unified">
|
||||
<input type="button" id="btn-split" class="btn btn-default" value="Split">
|
||||
</div>
|
||||
</div>
|
||||
Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @helpers.plural(diffs.size, "file")</a>
|
||||
@@ -232,7 +238,6 @@ $(function(){
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $check = $this.closest('table:not(.diff)').find('.toggle-notes');
|
||||
//var url = '';
|
||||
if (!$check.prop('checked')) {
|
||||
$check.prop('checked', true).trigger('change');
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@(account: Option[gitbucket.core.model.Account])(implicit context: gitbucket.core.controller.Context)
|
||||
<div id="avatar" class="muted">
|
||||
@if(account.nonEmpty && account.get.image.nonEmpty){
|
||||
<img src="@context.path/@account.get.userName/_avatar" style="with: 120px; height: 120px;"/>
|
||||
<img src="@context.path/@account.get.userName/_avatar" style="width: 120px; height: 120px;"/>
|
||||
} else {
|
||||
<div id="clickable">Upload Image</div>
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@issueOrPullRequest()={ @if(issue.isDefined && issue.get.isPullRequest)( "pull request" )else( "issue" ) }
|
||||
@issueOrPullRequest()={ @if(issue.exists(_.isPullRequest))( "pull request" )else( "issue" ) }
|
||||
|
||||
@comments.map {
|
||||
case comment: gitbucket.core.model.IssueComment => {
|
||||
@@ -46,7 +46,7 @@
|
||||
} else {
|
||||
referenced the @issueOrPullRequest()
|
||||
}
|
||||
<a href="@helpers.url(repository)/issues/@issue.get.issueId#comment-@comment.commentId">
|
||||
<a href="#comment-@comment.commentId">
|
||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||
</a>
|
||||
</span>
|
||||
@@ -186,11 +186,7 @@ $(function(){
|
||||
$content = $('#issueContent');
|
||||
}
|
||||
|
||||
$.get(url,
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
function(data){
|
||||
$.get(url, { dataType : 'html' }, function(data){
|
||||
$content.empty().html(data);
|
||||
});
|
||||
return false;
|
||||
@@ -198,8 +194,7 @@ $(function(){
|
||||
$('.issue-comment-box i.octicon-x').click(function(){
|
||||
if(confirm('Are you sure you want to delete this?')) {
|
||||
var id = $(this).closest('a').data('comment-id');
|
||||
$.post('@helpers.url(repository)/issue_comments/delete/' + id,
|
||||
function(data){
|
||||
$.post('@helpers.url(repository)/issue_comments/delete/' + id, function(data){
|
||||
if(data > 0) {
|
||||
$('#comment-' + id).remove();
|
||||
}
|
||||
@@ -213,22 +208,24 @@ $(function(){
|
||||
var url = '@helpers.url(repository)/commit_comments/_data/' + id;
|
||||
var $content = $('.commit-commentContent-' + id, $(this).closest('.commit-comment-box'));
|
||||
|
||||
$.get(url,
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
function(data){
|
||||
$.get(url, { dataType : 'html' }, function(data){
|
||||
$content.empty().html(data);
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('click', '.commit-comment-box i.octicon-x', function(){
|
||||
if(confirm('Are you sure you want to delete this?')) {
|
||||
var id = $(this).closest('a').data('comment-id');
|
||||
$.post('@helpers.url(repository)/commit_comments/delete/' + id,
|
||||
function(data){
|
||||
if(data > 0) {
|
||||
$('.commit-comment-' + id).closest('.not-diff').remove();
|
||||
var comment = $('.commit-comment-' + id).closest('.not-diff');
|
||||
if(comment.prev('.not-diff').length == 0){
|
||||
comment.next('.not-diff').find('.reply-comment').remove();
|
||||
}
|
||||
comment.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -260,10 +257,7 @@ $(function(){
|
||||
var $commentContent = $(ev.target).parents('div[class*=commit-commentContent-]'),
|
||||
commentId = $commentContent.attr('class').match(/commit-commentContent-.+/)[0].replace(/commit-commentContent-/, ''),
|
||||
checkboxes = $commentContent.find(':checkbox');
|
||||
$.get('@helpers.url(repository)/commit_comments/_data/' + commentId,
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
$.get('@helpers.url(repository)/commit_comments/_data/' + commentId, { dataType : 'html' },
|
||||
function(responseContent){
|
||||
$.ajax({
|
||||
url: '@helpers.url(repository)/commit_comments/edit/' + commentId,
|
||||
@@ -283,10 +277,7 @@ $(function(){
|
||||
@if(issue.isDefined){
|
||||
$('#issueContent').on('click', ':checkbox', function(ev){
|
||||
var checkboxes = $('#issueContent :checkbox');
|
||||
$.get('@helpers.url(repository)/issues/_data/@issue.get.issueId',
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
$.get('@helpers.url(repository)/issues/_data/@issue.get.issueId', { dataType : 'html' },
|
||||
function(responseContent){
|
||||
$.ajax({
|
||||
url: '@helpers.url(repository)/issues/edit/@issue.get.issueId',
|
||||
@@ -304,10 +295,7 @@ $(function(){
|
||||
var $commentContent = $(ev.target).parents('div[id^=commentContent-]'),
|
||||
commentId = $commentContent.attr('id').replace(/commentContent-/, ''),
|
||||
checkboxes = $commentContent.find(':checkbox');
|
||||
$.get('@helpers.url(repository)/issue_comments/_data/' + commentId,
|
||||
{
|
||||
dataType : 'html'
|
||||
},
|
||||
$.get('@helpers.url(repository)/issue_comments/_data/' + commentId, { dataType : 'html' },
|
||||
function(responseContent){
|
||||
$.ajax({
|
||||
url: '@helpers.url(repository)/issue_comments/edit/' + commentId,
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
Only those with write access to this repository can merge pull requests.
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<hr>
|
||||
@status.conflictMessage.map { message => @helpers.markdown(message, originRepository, false, true, false) }
|
||||
</div>
|
||||
} else {
|
||||
@if(status.branchIsOutOfDate){
|
||||
@if(status.hasUpdatePermission){
|
||||
@@ -139,8 +143,34 @@
|
||||
<span id="error-message" class="error"></span>
|
||||
<textarea name="message" style="height: 80px; margin-top: 8px; margin-bottom: 8px;" class="form-control">@issue.title</textarea>
|
||||
<div>
|
||||
<input type="button" class="btn btn-default" value="Cancel" id="cancel-merge-pull-request"/>
|
||||
<input type="submit" class="btn btn-success" value="Confirm merge"/>
|
||||
<div class="btn-group">
|
||||
<button id="merge-strategy-btn" class="dropdown-toggle btn btn-default" data-toggle="dropdown">
|
||||
<span class="strong">Merge commit</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="merge-strategy" data-value="merge-commit">
|
||||
<strong>Merge commit</strong><br>These commits will be added to the base branch via a merge commit.
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="merge-strategy" data-value="squash">
|
||||
<strong>Squash</strong><br>These commits will be combined into one commit in the base branch.
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="merge-strategy" data-value="rebase">
|
||||
<strong>Rebase</strong><br>These commits will be rebased and added to the base branch.
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="button" class="btn btn-default" value="Cancel" id="cancel-merge-pull-request"/>
|
||||
<input type="submit" class="btn btn-success" value="Confirm merge"/>
|
||||
<input type="hidden" name="strategy" value="merge-commit"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -194,5 +224,10 @@ $(function(){
|
||||
$('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text());
|
||||
});
|
||||
}
|
||||
|
||||
$('.merge-strategy').click(function(){
|
||||
$('button#merge-strategy-btn > span.strong').text($(this).find('strong').text());
|
||||
$('input[name=strategy]').val($(this).data('value'));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
13
src/main/twirl/gitbucket/core/pulls/proposals.scala.html
Normal file
13
src/main/twirl/gitbucket/core/pulls/proposals.scala.html
Normal file
@@ -0,0 +1,13 @@
|
||||
@(branches: Seq[String],
|
||||
parent: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@if(branches.nonEmpty){
|
||||
@branches.map { branch =>
|
||||
<div class="box-content" style="line-height: 20pt; margin-bottom: 6px; padding: 10px 6px 10px 10px; background-color: #fff9ea">
|
||||
<strong><i class="menu-icon octicon octicon-git-branch"></i><span class="muted">@branch</span></strong>
|
||||
<a class="pull-right btn btn-success" style="position: relative; top: -4px;"
|
||||
href="@helpers.url(repository)/compare/@{parent.owner}:@{parent.repository.defaultBranch}...@{repository.owner}:@{branch}">Compare & pull request</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -71,8 +71,19 @@
|
||||
|
||||
// Show reply comment form
|
||||
var replyComment = $tr.prev().find('.reply-comment').closest('.not-diff').show();
|
||||
replyComment.remove();
|
||||
$tr.after(replyComment);
|
||||
if(replyComment.length != 0){
|
||||
replyComment.remove();
|
||||
$tr.after(replyComment);
|
||||
} else {
|
||||
var $v = $('<div class="commit-comment-box reply-comment-box">')
|
||||
.append($('<input type="text" class="form-control reply-comment" placeholder="Reply...">')
|
||||
.data('filename', '@fileName')
|
||||
.data('newline', @newLineNumber.getOrElse("undefined"))
|
||||
.data('oldline', @oldLineNumber.getOrElse("undefined")));
|
||||
var tmp = getInlineContainer();
|
||||
tmp.children('td:last').html($v)
|
||||
$tr.after(tmp);
|
||||
}
|
||||
|
||||
$('#comment-list').append(data);
|
||||
if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) {
|
||||
@@ -82,6 +93,19 @@
|
||||
$('.btn-inline-comment').removeAttr('disabled');
|
||||
$('#error-content', $form).html($.parseJSON(req.responseText).content);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
function getInlineContainer() {
|
||||
console.log(window.viewType);
|
||||
if (window.viewType == 0) {
|
||||
if(@newLineNumber.isDefined){
|
||||
return $('<tr class="not-diff"><td colspan="2"></td><td colspan="2" class="comment-box-container"></td></tr>');
|
||||
}
|
||||
if(@oldLineNumber.isDefined){
|
||||
return $('<tr class="not-diff"><td colspan="2" class="comment-box-container"></td><td colspan="2"></td></tr>');
|
||||
}
|
||||
}
|
||||
return $('<tr class="not-diff"><td colspan="3" class="comment-box-container"></td></tr>');
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
41
src/main/twirl/gitbucket/core/repo/creating.scala.html
Normal file
41
src/main/twirl/gitbucket/core/repo/creating.scala.html
Normal file
@@ -0,0 +1,41 @@
|
||||
@(owner: String, repository: String)(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Creating...") {
|
||||
<div class="content-wrapper main-center">
|
||||
<div class="content body">
|
||||
<!-- Progress bar -->
|
||||
<div class="text-center" id="progress">
|
||||
<h2>Creating repository...</h2>
|
||||
<img src="@context.path/assets/common/images/indicator-bar.gif"/>
|
||||
</div>
|
||||
<!-- Error message -->
|
||||
<div id="error" style="display: none;">
|
||||
<h1>Failed to create repository</h1>
|
||||
<div id="errorMessage"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<script>
|
||||
$(function () {
|
||||
checkCreating();
|
||||
});
|
||||
|
||||
function checkCreating() {
|
||||
$.get('@context.path/@owner/@repository/creating', function (data) {
|
||||
console.log(data);
|
||||
if (data.creating == true) {
|
||||
setTimeout(checkCreating, 2000);
|
||||
} else {
|
||||
if (data.error) {
|
||||
$('#errorMessage').text(data.error);
|
||||
$('#error').show();
|
||||
$('#progress').hide();
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
location.href = '@context.path/@owner/@repository';
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -29,6 +29,7 @@
|
||||
</p>
|
||||
}
|
||||
}
|
||||
<div id="pull-request-area"></div>
|
||||
<div class="head" style="height: 24px;">
|
||||
<div class="pull-right">
|
||||
<div class="btn-group">
|
||||
@@ -204,19 +205,29 @@
|
||||
}
|
||||
}
|
||||
<script>
|
||||
@repository.sshUrl.map { sshUrl =>
|
||||
$('#repository-url-http').click(function(){
|
||||
$('#repository-url-proto').text('HTTP');
|
||||
$('#repository-url').val('@repository.httpUrl');
|
||||
$('#repository-clone-url').attr('href', '@RepositoryService.openRepoUrl(repository.httpUrl)')
|
||||
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||
});
|
||||
$(function() {
|
||||
@repository.sshUrl.map { sshUrl =>
|
||||
$('#repository-url-http').click(function(){
|
||||
$('#repository-url-proto').text('HTTP');
|
||||
$('#repository-url').val('@repository.httpUrl');
|
||||
$('#repository-clone-url').attr('href', '@RepositoryService.openRepoUrl(repository.httpUrl)')
|
||||
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||
});
|
||||
|
||||
$('#repository-url-ssh').click(function(){
|
||||
$('#repository-url-proto').text('SSH');
|
||||
$('#repository-url').val('@sshUrl');
|
||||
$('#repository-clone-url').attr('href', '@RepositoryService.openRepoUrl(sshUrl)');
|
||||
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||
});
|
||||
}
|
||||
$('#repository-url-ssh').click(function(){
|
||||
$('#repository-url-proto').text('SSH');
|
||||
$('#repository-url').val('@sshUrl');
|
||||
$('#repository-clone-url').attr('href', '@RepositoryService.openRepoUrl(sshUrl)');
|
||||
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||
});
|
||||
}
|
||||
|
||||
@if(pathList.isEmpty && hasWritePermission){
|
||||
$.get('@{helpers.url(repository)}/pulls/proposals', function(res){
|
||||
if(res) {
|
||||
$('#pull-request-area').html(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<input type="text" value="@webHook.url" class="form-control" style="display: inline; width: 500px; vertical-align: middle;" disabled />
|
||||
<input type="hidden" value="@webHook.url" name="url" />
|
||||
}
|
||||
<button class="btn btn-default" id="test">Test Hook</button>
|
||||
<button class="btn btn-default" id="test-hook">Test Hook</button>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label class="strong">Content type</label>
|
||||
@@ -48,7 +48,9 @@
|
||||
</div>
|
||||
<!--
|
||||
<label class="checkbox"><input type="checkbox" @check("events",CommitComment) />Commit comment <small class="help-block">Commit or diff commented on. </small> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",Create) />Create <small class="help-block">Branch, or tag created. </small> </label>
|
||||
-->
|
||||
<label class="checkbox"><input type="checkbox" @check("events",Create) />Create <small class="help-block normal">Branch, or tag created. </small> </label>
|
||||
<!--
|
||||
<label class="checkbox"><input type="checkbox" @check("events",Delete) />Delete <small class="help-block">Branch, or tag deleted. </small> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",Deployment) />Deployment <small class="help-block">Repository deployed. </small> </label>
|
||||
<label class="checkbox"><input type="checkbox" @check("events",DeploymentStatus) />Deployment status <small class="help-block">Deployment status updated from the API. </small> </label>
|
||||
@@ -131,7 +133,7 @@ $(function(){
|
||||
e.preventDefault();
|
||||
$(this).tab('show');
|
||||
});
|
||||
$('#test').click(function(e){
|
||||
$('#test-hook').click(function(e){
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@@ -618,12 +618,6 @@ span.simplified-path {
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.new-branch-name {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.btn-pullrequest-branch{
|
||||
background: none;
|
||||
border: 1px solid #0088cc;
|
||||
@@ -1856,6 +1850,12 @@ body.page-load * {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
body:not(.sidebar-collapse) .main-sidebar li.menu-item-hover > a,
|
||||
body.sidebar-collapse .main-sidebar li:hover > a > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
body.sidebar-collapse .main-sidebar li.menu-item-hover:not(:hover) span.pull-right-container {
|
||||
display: inline !important;
|
||||
position: absolute;
|
||||
|
||||
@@ -149,7 +149,7 @@ $.extend(JsDiffRender.prototype,{
|
||||
$('<tr>').append(
|
||||
lineNum('old',o.base, o.change),
|
||||
$('<td class="body">').html(o.base ? baseTextDom(o.base): "").addClass(o.change),
|
||||
lineNum('old',o.head, o.change),
|
||||
lineNum('new',o.head, o.change),
|
||||
$('<td class="body">').html(o.head ? headTextDom(o.head): "").addClass(o.change)
|
||||
).appendTo(tbody);
|
||||
break;
|
||||
@@ -158,7 +158,7 @@ $.extend(JsDiffRender.prototype,{
|
||||
$('<tr>').append(
|
||||
lineNum('old',o.base, 'delete'),
|
||||
$('<td class="body">').append(ld.base).addClass('delete'),
|
||||
lineNum('old',o.head, 'insert'),
|
||||
lineNum('new',o.head, 'insert'),
|
||||
$('<td class="body">').append(ld.head).addClass('insert')
|
||||
).appendTo(tbody);
|
||||
break;
|
||||
@@ -379,7 +379,7 @@ function string_score(string, word) {
|
||||
strLength = string.length,
|
||||
lWord = word.toUpperCase(),
|
||||
wordLength = word.length;
|
||||
|
||||
|
||||
return calc(zero, 0, 0, 0, 0, []);
|
||||
function calc(score, startAt, skip, runningScore, i, matchingPositions){
|
||||
if( i < wordLength) {
|
||||
|
||||
@@ -32,8 +32,8 @@ class MergeServiceSpec extends FunSpec {
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
assert(service.checkConflictCache("user1", "repo1", branch, issueId) == None)
|
||||
val conflicted = service.checkConflict("user1", "repo1", branch, issueId)
|
||||
assert(service.checkConflictCache("user1", "repo1", branch, issueId) == Some(false))
|
||||
assert(conflicted == false)
|
||||
assert(service.checkConflictCache("user1", "repo1", branch, issueId) == Some(None))
|
||||
assert(conflicted.isEmpty)
|
||||
}
|
||||
it("checkConflict true if not conflicted, and create cache") {
|
||||
val repo2Dir = initRepository("user1","repo2")
|
||||
@@ -42,15 +42,18 @@ class MergeServiceSpec extends FunSpec {
|
||||
}
|
||||
assert(service.checkConflictCache("user1", "repo2", branch, issueId) == None)
|
||||
val conflicted = service.checkConflict("user1", "repo2", branch, issueId)
|
||||
assert(conflicted == true)
|
||||
assert(service.checkConflictCache("user1", "repo2", branch, issueId) == Some(true))
|
||||
assert(conflicted.isDefined)
|
||||
assert(service.checkConflictCache("user1", "repo2", branch, issueId) match {
|
||||
case Some(Some(_: String)) => true
|
||||
case _ => false
|
||||
})
|
||||
}
|
||||
}
|
||||
describe("checkConflictCache") {
|
||||
it("merged cache invalid if origin branch moved") {
|
||||
val repo3Dir = initRepository("user1","repo3")
|
||||
assert(service.checkConflict("user1", "repo3", branch, issueId) == false)
|
||||
assert(service.checkConflictCache("user1", "repo3", branch, issueId) == Some(false))
|
||||
assert(service.checkConflict("user1", "repo3", branch, issueId).isEmpty)
|
||||
assert(service.checkConflictCache("user1", "repo3", branch, issueId) == Some(None))
|
||||
using(Git.open(repo3Dir)){ git =>
|
||||
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" )
|
||||
}
|
||||
@@ -58,8 +61,8 @@ class MergeServiceSpec extends FunSpec {
|
||||
}
|
||||
it("merged cache invalid if request branch moved") {
|
||||
val repo4Dir = initRepository("user1","repo4")
|
||||
assert(service.checkConflict("user1", "repo4", branch, issueId) == false)
|
||||
assert(service.checkConflictCache("user1", "repo4", branch, issueId) == Some(false))
|
||||
assert(service.checkConflict("user1", "repo4", branch, issueId).isEmpty)
|
||||
assert(service.checkConflictCache("user1", "repo4", branch, issueId) == Some(None))
|
||||
using(Git.open(repo4Dir)){ git =>
|
||||
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" )
|
||||
}
|
||||
@@ -67,8 +70,8 @@ class MergeServiceSpec extends FunSpec {
|
||||
}
|
||||
it("should merged cache invalid if origin branch moved") {
|
||||
val repo5Dir = initRepository("user1","repo5")
|
||||
assert(service.checkConflict("user1", "repo5", branch, issueId) == false)
|
||||
assert(service.checkConflictCache("user1", "repo5", branch, issueId) == Some(false))
|
||||
assert(service.checkConflict("user1", "repo5", branch, issueId).isEmpty)
|
||||
assert(service.checkConflictCache("user1", "repo5", branch, issueId) == Some(None))
|
||||
using(Git.open(repo5Dir)){ git =>
|
||||
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" )
|
||||
}
|
||||
@@ -79,8 +82,11 @@ class MergeServiceSpec extends FunSpec {
|
||||
using(Git.open(repo6Dir)){ git =>
|
||||
createConfrict(git)
|
||||
}
|
||||
assert(service.checkConflict("user1", "repo6", branch, issueId) == true)
|
||||
assert(service.checkConflictCache("user1", "repo6", branch, issueId) == Some(true))
|
||||
assert(service.checkConflict("user1", "repo6", branch, issueId).isDefined)
|
||||
assert(service.checkConflictCache("user1", "repo6", branch, issueId) match {
|
||||
case Some(Some(_: String)) => true
|
||||
case _ => false
|
||||
})
|
||||
using(Git.open(repo6Dir)){ git =>
|
||||
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" )
|
||||
}
|
||||
@@ -91,8 +97,11 @@ class MergeServiceSpec extends FunSpec {
|
||||
using(Git.open(repo7Dir)){ git =>
|
||||
createConfrict(git)
|
||||
}
|
||||
assert(service.checkConflict("user1", "repo7", branch, issueId) == true)
|
||||
assert(service.checkConflictCache("user1", "repo7", branch, issueId) == Some(true))
|
||||
assert(service.checkConflict("user1", "repo7", branch, issueId).isDefined)
|
||||
assert(service.checkConflictCache("user1", "repo7", branch, issueId) match {
|
||||
case Some(Some(_)) => true
|
||||
case _ => false
|
||||
})
|
||||
using(Git.open(repo7Dir)){ git =>
|
||||
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge4" )
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user