Compare commits

...

61 Commits

Author SHA1 Message Date
Naoki Takezoe
b7bb6b0787 Update plugins.json schema 2017-12-23 04:31:01 +09:00
Naoki Takezoe
ea41786f8c Fixup 2017-12-23 02:43:11 +09:00
Naoki Takezoe
962ae2130e Update README and CHANGELOG for GitBucket 4.20.0 release 2017-12-23 02:43:11 +09:00
Naoki Takezoe
90ea05f2a1 Merge pull request #1814 from gageas/fix-typo
Fix typo in style attribute
2017-12-22 03:27:37 +09:00
gageas
f8bda516d6 Fix typo in style attribute
Because of the typo, image displayed in unintended aspect ratio, in some case.
2017-12-21 22:49:01 +09:00
Naoki Takezoe
378c031ecb Merge pull request #1813 from atware/WIP-webhook-create-event
support CreateEvent in webhook
2017-12-21 18:42:58 +09:00
Naoki Takezoe
9a5db80dea (refs #1783) hide overflowed chars in the sidebar 2017-12-21 18:41:19 +09:00
Naoki Takezoe
992eb0ceda Bump to 4.20.0 2017-12-21 15:22:07 +09:00
guyon
39e1ac2398 support CreateEvent in webhook 2017-12-21 15:21:51 +09:00
Naoki Takezoe
d1c77de5a0 Bump to 4.19.3 2017-12-21 15:21:07 +09:00
Naoki Takezoe
3f8069638c Merge pull request #1807 from uli-heller/mariadb-java-client-2.2.0
Upgraded to mariadb-java-client-2.2.0
2017-12-15 13:58:26 +00:00
Uli Heller
d62fc1185c Upgraded to mariadb-java-client-2.2.0 2017-12-14 07:59:19 +01:00
Naoki Takezoe
768706e1d1 Resurrect the pages plugin 2017-12-12 19:25:34 +09:00
Naoki Takezoe
8cc9771237 Merge pull request #1804 from gitbucket/show-conflict-files
Show conflicting files if pull request can't be merged
2017-12-12 13:32:31 +09:00
Naoki Takezoe
8df30ef01b Fix message 2017-12-12 13:06:04 +09:00
Naoki Takezoe
dd2e5bfedf Fix testcase 2017-12-12 12:04:24 +09:00
Naoki Takezoe
e3c7eb092f Fix merge failure 2017-12-12 12:02:04 +09:00
Naoki Takezoe
5b3c3e2e7c Merge branch 'master' into show-conflict-files 2017-12-12 11:58:57 +09:00
Naoki Takezoe
0e04925b6b Merge pull request #1802 from gitbucket/merge-strategy
Add a pulldown menu to choose the merge strategy
2017-12-12 11:48:12 +09:00
Naoki Takezoe
9a127256f3 Update reflog message 2017-12-12 11:39:00 +09:00
Naoki Takezoe
1033122fec Show conflicting files 2017-12-12 03:20:50 +09:00
Naoki Takezoe
847f96d537 Filter proposed branches which have been already raised as pull request 2017-12-12 02:25:36 +09:00
Naoki Takezoe
70f40846bb Show pull request proposals for the current repository
if the repository doesn't have a parent repository.
2017-12-12 00:39:30 +09:00
Naoki Takezoe
3a540aa660 Merge pull request #1803 from uli-heller/jgit-4.9.1.201712030800-r
jgit-4.9.1.201712030800-r
2017-12-12 00:14:52 +09:00
Naoki Takezoe
1adc9b3223 Refactoring 2017-12-11 20:20:58 +09:00
Naoki Takezoe
0309496df6 Fix rebase process 2017-12-11 20:16:11 +09:00
Uli Heller
f83ecac7ae jgit-4.9.1.201712030800-r 2017-12-11 08:52:00 +01:00
Naoki Takezoe
cd4d75e35e Format code 2017-12-11 12:33:06 +09:00
Naoki Takezoe
eb61bc50d6 Implemented squash merge strategy 2017-12-11 12:32:52 +09:00
Naoki Takezoe
4bbb22f73b Fix branch selector presentation 2017-12-11 03:47:02 +09:00
Naoki Takezoe
fcb374c5c2 Implemented rebase strategy 2017-12-10 18:04:45 +09:00
Naoki Takezoe
a03d1c97c2 Format code 2017-12-10 17:39:45 +09:00
Naoki Takezoe
2d58b7f2d7 Add a pulldown menu to choose the merge strategy 2017-12-10 14:59:53 +09:00
Naoki Takezoe
332a1b4b0b Add "Compare & pull request" button on the top of the repository viewer (#1476) 2017-12-09 04:52:21 +09:00
Naoki Takezoe
6bd58b0c45 Don't filter pull request target repositories
because users who can access the forked repository should see the
original repositories and other forked repositories basically.
2017-12-08 15:10:41 +09:00
Naoki Takezoe
fb175df851 (refs #1797) Fix accessible check for pull request repositories 2017-12-08 03:05:21 +09:00
Naoki Takezoe
b41aad92f2 Increase wait before refreshing the screen 2017-12-08 02:02:09 +09:00
Naoki Takezoe
aabae2ef7f Fix compilation error 2017-12-07 20:07:16 +09:00
Naoki Takezoe
0c3d1fd86d (refs #1799) Fix permalinks for pull request comments 2017-12-07 20:03:47 +09:00
Naoki Takezoe
adba849ec5 (refs #1798) Fix specification method of offset 2017-12-07 14:37:48 +09:00
Naoki Takezoe
8539486c6e Update README.md 2017-12-07 02:19:44 +09:00
Naoki Takezoe
86f4b41beb Fix comment reply form behavior in the diff view (#1796) 2017-12-06 14:38:05 +09:00
Naoki Takezoe
aa54eff3d6 Fix diff class attribute in split mode 2017-12-06 10:46:37 +09:00
Naoki Takezoe
27ab21c9a7 Fix file uploading issue 2017-12-06 03:53:43 +09:00
Naoki Takezoe
557ed827d0 Merge pull request #1792 from gitbucket/create-patch
Add a Patch button to the diff view
2017-12-05 18:42:05 +09:00
Naoki Takezoe
9cc466a727 Add a Patch button to the diff view 2017-12-05 13:06:17 +09:00
Naoki Takezoe
9a9be12324 Fix error 2017-12-05 12:37:18 +09:00
Naoki Takezoe
8e91b9f0b5 Improve DiffEntry searching and add download patch endpoint 2017-12-05 03:31:23 +09:00
Naoki Takezoe
2862ceb5ad Merge pull request #1790 from gitbucket/copy-repository
Create new repository from existing git repository
2017-12-05 01:40:51 +09:00
Naoki Takezoe
d157426d66 Fix error page for repository creation 2017-12-04 20:55:11 +09:00
Naoki Takezoe
58635674cb Asynchronize repository forking 2017-12-04 20:18:39 +09:00
Naoki Takezoe
f6a048e0f7 FeedAdd validation for sourceUrl 2017-12-04 19:59:48 +09:00
Naoki Takezoe
c4dc1d7334 Feedback error in repository creation to users 2017-12-04 16:42:15 +09:00
Naoki Takezoe
efd5a64749 Asynchronize repository creation 2017-12-04 16:12:43 +09:00
Naoki Takezoe
13800a7023 Implement repository cloning 2017-12-04 14:00:57 +09:00
Naoki Takezoe
43d19d7d52 Add create new repository from existing git repository option 2017-12-04 01:32:35 +09:00
Naoki Takezoe
8a8278906a Bump to 4.19.2 2017-12-03 05:28:49 +09:00
Naoki Takezoe
d15b3fb2f6 Modify id of "Test Hook" button 2017-12-03 05:20:32 +09:00
Naoki Takezoe
bcd92916ca Fix routing in CompositeScalatraFilter 2017-12-03 04:36:05 +09:00
Naoki Takezoe
810cbda123 Update README.md 2017-12-03 02:17:27 +09:00
Naoki Takezoe
fee7cebdf1 Update README.md 2017-12-03 02:16:57 +09:00
39 changed files with 946 additions and 455 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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")
)

View File

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

View File

@@ -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 =

View File

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

View File

@@ -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

View File

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

View File

@@ -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.
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = {

View File

@@ -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")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 youre 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>

View File

@@ -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:&nbsp;<span class="new-branch"></span></span>
<span class="strong">Create branch:&nbsp;<span class="new-branch"></span></span>
<br><span style="padding-left: 17px;">from&nbsp;'@branch'</span>
<input type="hidden" name="new">
<input type="hidden" name="from" value="@branch">

View File

@@ -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');
}

View File

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

View File

@@ -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,

View File

@@ -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>

View 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>
}
}

View File

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

View 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>

View File

@@ -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>

View File

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

View File

@@ -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;

View File

@@ -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) {

View File

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