mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 04:26:32 +02:00
Compare commits
183 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cc3ab30cf | ||
|
|
987f4f1afd | ||
|
|
9a1536484a | ||
|
|
5dd03cff69 | ||
|
|
4cf37af114 | ||
|
|
916edf9415 | ||
|
|
c20a43433d | ||
|
|
d536504e32 | ||
|
|
dad7e03ec1 | ||
|
|
e205c0082e | ||
|
|
d7cf60c9d6 | ||
|
|
9408a168fb | ||
|
|
9d827eaa29 | ||
|
|
7fdb5ab142 | ||
|
|
2296a2524b | ||
|
|
1a031b964d | ||
|
|
347490ed40 | ||
|
|
de0bdac66b | ||
|
|
1d6abae837 | ||
|
|
0e4b7f951d | ||
|
|
1afa893596 | ||
|
|
edf0903620 | ||
|
|
90f0006bc0 | ||
|
|
ac00c03a96 | ||
|
|
c5760bd378 | ||
|
|
a66041de88 | ||
|
|
023d150d95 | ||
|
|
12d41cc3ce | ||
|
|
2eee95bd2d | ||
|
|
f42ff60772 | ||
|
|
79267a63b1 | ||
|
|
816f2d30ae | ||
|
|
d135f959f9 | ||
|
|
c47ddf02b1 | ||
|
|
93687dd805 | ||
|
|
1e59941ca5 | ||
|
|
3c7816be6c | ||
|
|
ace583ecce | ||
|
|
60592debe3 | ||
|
|
6104d7657b | ||
|
|
2a093df13e | ||
|
|
52eb2a1384 | ||
|
|
d5f1fc33d1 | ||
|
|
694b77294c | ||
|
|
41f1c0c136 | ||
|
|
e8262cf5ce | ||
|
|
b3294f03fd | ||
|
|
13f19fd260 | ||
|
|
2592e5a41d | ||
|
|
1799e9b487 | ||
|
|
d17070bc35 | ||
|
|
7e0fb5b2bb | ||
|
|
fda847640d | ||
|
|
ab21d06848 | ||
|
|
43febc2f55 | ||
|
|
5375ec88c8 | ||
|
|
36ce8701ef | ||
|
|
d8220a9021 | ||
|
|
37df03815e | ||
|
|
3782c74f61 | ||
|
|
7a66352144 | ||
|
|
20140fffe9 | ||
|
|
e65b0f63bb | ||
|
|
7f8e36eb66 | ||
|
|
7fe3211485 | ||
|
|
12dcba4a84 | ||
|
|
4437045248 | ||
|
|
e5c6b9f67e | ||
|
|
32ef920549 | ||
|
|
03e32f970e | ||
|
|
e513a581e7 | ||
|
|
ebd2efcd6e | ||
|
|
86a8496344 | ||
|
|
a92f4ceece | ||
|
|
d496e78acd | ||
|
|
21cc64feb5 | ||
|
|
0c78e38e8f | ||
|
|
7ab1f3e886 | ||
|
|
68609b8996 | ||
|
|
1abec64da7 | ||
|
|
2492046db2 | ||
|
|
fbf11b7ec4 | ||
|
|
63ae013895 | ||
|
|
2fedbbc84b | ||
|
|
4e65818a16 | ||
|
|
5aac99daf2 | ||
|
|
8c46c73090 | ||
|
|
47f1241a93 | ||
|
|
a8114216fe | ||
|
|
9d32b3841f | ||
|
|
a1b78a8f2a | ||
|
|
ed17f0a912 | ||
|
|
bb4fe1314b | ||
|
|
28567acffa | ||
|
|
1fe3213e68 | ||
|
|
a8f53d965a | ||
|
|
6974158850 | ||
|
|
78c7a4be0b | ||
|
|
e961f54405 | ||
|
|
a826589cdb | ||
|
|
afd68da2c5 | ||
|
|
bf5c1a98ed | ||
|
|
6c17b54577 | ||
|
|
06b2bf5333 | ||
|
|
481eae1a6e | ||
|
|
b797f23844 | ||
|
|
1da813c7b7 | ||
|
|
efff209ebd | ||
|
|
2c3a1b011c | ||
|
|
cd49e9b25a | ||
|
|
6ac7429f66 | ||
|
|
0cc42f3492 | ||
|
|
bc18abe185 | ||
|
|
c47e48ace8 | ||
|
|
22cde10e60 | ||
|
|
c1433f3995 | ||
|
|
fc0beaec82 | ||
|
|
3d7fe9c018 | ||
|
|
6e17f746f6 | ||
|
|
262ee3a74c | ||
|
|
4aad4e9b74 | ||
|
|
2c8671f712 | ||
|
|
fd0d5ca7da | ||
|
|
0ad66fd64a | ||
|
|
243548559f | ||
|
|
de6f173b2e | ||
|
|
804027c898 | ||
|
|
047082b29d | ||
|
|
34bbc10f76 | ||
|
|
80b50f6fa9 | ||
|
|
7cd47a714b | ||
|
|
b1d46ce2e5 | ||
|
|
ecdb2b3eb5 | ||
|
|
dde3738c45 | ||
|
|
2e69959a1f | ||
|
|
28c5f6e434 | ||
|
|
1b165fd230 | ||
|
|
96f8716417 | ||
|
|
7353da674d | ||
|
|
dbb25c95cd | ||
|
|
126a3465d6 | ||
|
|
6061f70e24 | ||
|
|
ec569839fe | ||
|
|
fd0bc80284 | ||
|
|
318cdafcb1 | ||
|
|
1e9b60446f | ||
|
|
35216d8a47 | ||
|
|
4aa90c0501 | ||
|
|
7965408960 | ||
|
|
10c6660f23 | ||
|
|
e391688a45 | ||
|
|
8445f210ee | ||
|
|
c268ad46ce | ||
|
|
1a8f476a81 | ||
|
|
22d8fdd81a | ||
|
|
ae947cd436 | ||
|
|
b70a46114c | ||
|
|
126cc16977 | ||
|
|
a72ca0dc53 | ||
|
|
a914b32fe7 | ||
|
|
5d344c33cc | ||
|
|
82564cecb0 | ||
|
|
fb5012f851 | ||
|
|
067a4856f4 | ||
|
|
a22afc2fa8 | ||
|
|
0e7ce02e4e | ||
|
|
b13fc2b4e7 | ||
|
|
b5322186ab | ||
|
|
09f85da1de | ||
|
|
775f838110 | ||
|
|
123cab6c19 | ||
|
|
4cb04e9cc3 | ||
|
|
4aa2727676 | ||
|
|
8dea7302a3 | ||
|
|
04823666b6 | ||
|
|
ed9d4443ae | ||
|
|
45a1af2cd7 | ||
|
|
cb920feb24 | ||
|
|
16097bff94 | ||
|
|
c159b704b6 | ||
|
|
3920dfb57e | ||
|
|
31345cc739 | ||
|
|
3ebc4e8e23 |
@@ -2,6 +2,9 @@ language: scala
|
|||||||
sudo: true
|
sudo: true
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
- oraclejdk11
|
||||||
|
- openjdk8
|
||||||
|
- openjdk11
|
||||||
script:
|
script:
|
||||||
- sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
- sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
||||||
before_script:
|
before_script:
|
||||||
|
|||||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,12 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All changes to the project will be documented in this file.
|
All changes to the project will be documented in this file.
|
||||||
|
|
||||||
### 4.27.0 - 29 Jul 2018
|
### 4.30.1 - 22 Dec 2018
|
||||||
|
- Bug fix for several WebHooks and Web API
|
||||||
|
|
||||||
|
## 4.30.0 - 15 Dec 2018
|
||||||
|
- Automatic ChangeLog Summary generation for new Releases
|
||||||
|
- A lot of GitBucket Web API updates to increase compatibility with the GitHub API.
|
||||||
|
- Display of checkboxes in Markdown files in Git repositories
|
||||||
|
- A new extension point for plugins: anonymousAccessiblePaths
|
||||||
|
- Group support in the Gist Plugin
|
||||||
|
- Allow redirection to the Release Page from the Activity Timeline Page
|
||||||
|
|
||||||
|
## 4.29.0 - 29 Sep 2018
|
||||||
|
- Official Docker image has been available
|
||||||
|
- Enhance file edit and delete buttons of the repository viewer
|
||||||
|
- Fix Patch button to generate patches for all files in the commit
|
||||||
|
- Display confirmation dialog for Transfer Ownership and Garbage collection
|
||||||
|
- Fix wrong url encoding in "Compare & pull request"
|
||||||
|
|
||||||
|
## 4.28.0 - 1 Sep 2018
|
||||||
|
- Proxy support for plugin installation
|
||||||
|
- Fix some bugs around pull requests
|
||||||
|
|
||||||
|
## 4.27.0 - 29 Jul 2018
|
||||||
- Create new tag on the browser
|
- Create new tag on the browser
|
||||||
- EditorConfig support
|
- EditorConfig support
|
||||||
- Improve issues / pull requests search
|
- Improve issues / pull requests search
|
||||||
|
- Some improvements and bug fixes for plugin installation via internet and pull request commenting
|
||||||
|
|
||||||
### 4.26.0 - 30 Jun 2018
|
## 4.26.0 - 30 Jun 2018
|
||||||
- Installing plugins from the central registry
|
- Installing plugins from the central registry
|
||||||
- Repositories tab in the dashboard
|
- Repositories tab in the dashboard
|
||||||
- Fork dialog enhancement
|
- Fork dialog enhancement
|
||||||
@@ -14,7 +37,7 @@ All changes to the project will be documented in this file.
|
|||||||
- Keep showing incompleted task list
|
- Keep showing incompleted task list
|
||||||
- New notification hooks
|
- New notification hooks
|
||||||
|
|
||||||
### 4.25.0 - 29 May 2018
|
## 4.25.0 - 29 May 2018
|
||||||
- Security improvements
|
- Security improvements
|
||||||
- Show mail address at the profile page
|
- Show mail address at the profile page
|
||||||
- Task list on commit comments
|
- Task list on commit comments
|
||||||
@@ -22,10 +45,10 @@ All changes to the project will be documented in this file.
|
|||||||
- Expose user public keys
|
- Expose user public keys
|
||||||
- Download repository improvements
|
- Download repository improvements
|
||||||
|
|
||||||
### 4.24.1 - 1 May 2018
|
## 4.24.1 - 1 May 2018
|
||||||
- Fix bug in Web API authentication
|
- Fix bug in Web API authentication
|
||||||
|
|
||||||
### 4.24.0 - 30 Apr 2018
|
## 4.24.0 - 30 Apr 2018
|
||||||
- Diff for each review comment on pull requests
|
- Diff for each review comment on pull requests
|
||||||
- Extra mail addresses support
|
- Extra mail addresses support
|
||||||
- Show tags at the commit list
|
- Show tags at the commit list
|
||||||
@@ -33,12 +56,12 @@ All changes to the project will be documented in this file.
|
|||||||
- Renew layout of gitbucket-gist-plugin
|
- Renew layout of gitbucket-gist-plugin
|
||||||
- Web API of gitbucket-ci-plugin
|
- Web API of gitbucket-ci-plugin
|
||||||
|
|
||||||
### 4.23.1 - 10 Apr 2018
|
## 4.23.1 - 10 Apr 2018
|
||||||
- Fix bug that the contents API doesn't work for the repository root
|
- Fix bug that the contents API doesn't work for the repository root
|
||||||
- Fix shutdown problem in Tomcat deployment
|
- Fix shutdown problem in Tomcat deployment
|
||||||
- Render by plugins at the blob view even if it's a binary file
|
- Render by plugins at the blob view even if it's a binary file
|
||||||
|
|
||||||
### 4.23.0 - 31 Mar 2018
|
## 4.23.0 - 31 Mar 2018
|
||||||
- Allow tail slash in URL
|
- Allow tail slash in URL
|
||||||
- Display commit message of tags at the releases page
|
- Display commit message of tags at the releases page
|
||||||
- Add labels property to issues and pull requests API response
|
- Add labels property to issues and pull requests API response
|
||||||
@@ -46,26 +69,26 @@ All changes to the project will be documented in this file.
|
|||||||
- Git authentication with personal access token
|
- Git authentication with personal access token
|
||||||
- Max parallel builds and max stored history in CI plugin became configurable
|
- Max parallel builds and max stored history in CI plugin became configurable
|
||||||
|
|
||||||
### 4.22.0 - 3 Mar 2018
|
## 4.22.0 - 3 Mar 2018
|
||||||
- Pull request merge strategy settings
|
- Pull request merge strategy settings
|
||||||
- Create repository with an empty commit
|
- Create repository with an empty commit
|
||||||
- Improve database viewer
|
- Improve database viewer
|
||||||
- Update maven-repository-plugin
|
- Update maven-repository-plugin
|
||||||
|
|
||||||
### 4.21.2 - 27 Jan 2018
|
## 4.21.2 - 27 Jan 2018
|
||||||
- Bugfix
|
- Bugfix
|
||||||
|
|
||||||
### 4.21.1 - 27 Jan 2018
|
## 4.21.1 - 27 Jan 2018
|
||||||
- Bugfix
|
- Bugfix
|
||||||
|
|
||||||
### 4.21.0 - 27 Jan 2018
|
## 4.21.0 - 27 Jan 2018
|
||||||
- Release page
|
- Release page
|
||||||
- OpenID Connect support
|
- OpenID Connect support
|
||||||
- New database viewer
|
- New database viewer
|
||||||
- Submodule links to web page
|
- Submodule links to web page
|
||||||
- Clarify close/reopen button
|
- Clarify close/reopen button
|
||||||
|
|
||||||
## 4.20.0 - 23 Dec 2017
|
# 4.20.0 - 23 Dec 2017
|
||||||
- Squash and rebase merge strategy for pull requests
|
- Squash and rebase merge strategy for pull requests
|
||||||
- Quick pull request creation
|
- Quick pull request creation
|
||||||
- Download patch from the diff view
|
- Download patch from the diff view
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -68,11 +68,17 @@ Support
|
|||||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
- 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.
|
- 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.27.x
|
What's New in 4.30.x
|
||||||
-------------
|
-------------
|
||||||
### 4.27.0 - 29 Jul 2018
|
### 4.30.1 - 22 Dec 2018
|
||||||
- Create new tag on the browser
|
- Bug fix for several WebHooks and Web API
|
||||||
- EditorConfig support
|
|
||||||
- Improve issues / pull requests search
|
### 4.30.0 - 15 Dec 2018
|
||||||
|
- Automatic ChangeLog Summary generation for new Releases
|
||||||
|
- A lot of GitBucket Web API updates to increase compatibility with the GitHub API.
|
||||||
|
- Display of checkboxes in Markdown files in Git repositories
|
||||||
|
- A new extension point for plugins: anonymousAccessiblePaths
|
||||||
|
- Group support in the Gist Plugin
|
||||||
|
- Allow redirection to the Release Page from the Activity Timeline Page
|
||||||
|
|
||||||
See the [change log](CHANGELOG.md) for all of the updates.
|
See the [change log](CHANGELOG.md) for all of the updates.
|
||||||
|
|||||||
57
build.sbt
57
build.sbt
@@ -3,9 +3,10 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
|||||||
|
|
||||||
val Organization = "io.github.gitbucket"
|
val Organization = "io.github.gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "4.27.0"
|
val GitBucketVersion = "4.30.1"
|
||||||
val ScalatraVersion = "2.6.1"
|
val ScalatraVersion = "2.6.3"
|
||||||
val JettyVersion = "9.4.7.v20170914"
|
val JettyVersion = "9.4.14.v20181114"
|
||||||
|
val JgitVersion = "5.1.3.201810200350-r"
|
||||||
|
|
||||||
lazy val root = (project in file("."))
|
lazy val root = (project in file("."))
|
||||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||||
@@ -16,7 +17,7 @@ sourcesInBase := false
|
|||||||
organization := Organization
|
organization := Organization
|
||||||
name := Name
|
name := Name
|
||||||
version := GitBucketVersion
|
version := GitBucketVersion
|
||||||
scalaVersion := "2.12.6"
|
scalaVersion := "2.12.8"
|
||||||
|
|
||||||
scalafmtOnCompile := true
|
scalafmtOnCompile := true
|
||||||
|
|
||||||
@@ -30,46 +31,46 @@ resolvers ++= Seq(
|
|||||||
)
|
)
|
||||||
|
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "5.0.1.201806211838-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "5.0.1.201806211838-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||||
"org.json4s" %% "json4s-jackson" % "3.5.3",
|
"org.json4s" %% "json4s-jackson" % "3.5.2",
|
||||||
"commons-io" % "commons-io" % "2.6",
|
"commons-io" % "commons-io" % "2.6",
|
||||||
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
"io.github.gitbucket" % "solidbase" % "1.0.3",
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.15",
|
"io.github.gitbucket" % "markedj" % "1.0.15",
|
||||||
"org.apache.commons" % "commons-compress" % "1.15",
|
"org.apache.commons" % "commons-compress" % "1.18",
|
||||||
"org.apache.commons" % "commons-email" % "1.5",
|
"org.apache.commons" % "commons-email" % "1.5",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.4",
|
"org.apache.httpcomponents" % "httpclient" % "4.5.6",
|
||||||
"org.apache.sshd" % "apache-sshd" % "1.6.0" exclude ("org.slf4j", "slf4j-jdk14"),
|
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||||
"org.apache.tika" % "tika-core" % "1.17",
|
"org.apache.tika" % "tika-core" % "1.19.1",
|
||||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
"com.github.takezoe" %% "blocking-slick-32" % "0.0.11",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.196",
|
"com.h2database" % "h2" % "1.4.197",
|
||||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.2.6",
|
"org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0",
|
||||||
"org.postgresql" % "postgresql" % "42.2.4",
|
"org.postgresql" % "postgresql" % "42.2.5",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||||
"com.zaxxer" % "HikariCP" % "2.7.4",
|
"com.zaxxer" % "HikariCP" % "3.2.0",
|
||||||
"com.typesafe" % "config" % "1.3.2",
|
"com.typesafe" % "config" % "1.3.3",
|
||||||
"com.typesafe.akka" %% "akka-actor" % "2.5.8",
|
"com.typesafe.akka" %% "akka-actor" % "2.5.18",
|
||||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||||
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
||||||
"org.cache2k" % "cache2k-all" % "1.0.1.Final",
|
"org.cache2k" % "cache2k-all" % "1.2.0.Final",
|
||||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.1-akka-2.5.x" exclude ("c3p0", "c3p0"),
|
"com.enragedginger" %% "akka-quartz-scheduler" % "1.7.0-akka-2.5.x" exclude ("c3p0", "c3p0") exclude ("com.zaxxer", "HikariCP-java6"),
|
||||||
"net.coobird" % "thumbnailator" % "0.4.8",
|
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||||
"com.nimbusds" % "oauth2-oidc-sdk" % "5.45",
|
"com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
"junit" % "junit" % "4.12" % "test",
|
"junit" % "junit" % "4.12" % "test",
|
||||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||||
"org.mockito" % "mockito-core" % "2.13.0" % "test",
|
"org.mockito" % "mockito-core" % "2.23.4" % "test",
|
||||||
"com.wix" % "wix-embedded-mysql" % "3.0.0" % "test",
|
"com.wix" % "wix-embedded-mysql" % "4.2.0" % "test",
|
||||||
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.6" % "test",
|
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.10" % "test",
|
||||||
"net.i2p.crypto" % "eddsa" % "0.2.0",
|
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||||
"is.tagomor.woothee" % "woothee-java" % "1.7.0",
|
"is.tagomor.woothee" % "woothee-java" % "1.8.0",
|
||||||
"org.ec4j.core" % "ec4j-core" % "0.0.1"
|
"org.ec4j.core" % "ec4j-core" % "0.0.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compiler settings
|
// Compiler settings
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
sbt.version=1.1.6
|
sbt.version=1.2.6
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.4"
|
libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.5"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0")
|
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.13")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.15")
|
||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
|
||||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.1")
|
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.3")
|
||||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
||||||
addSbtCoursier
|
addSbtCoursier
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||||
|
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
notifications:1.6.0
|
notifications:1.7.0
|
||||||
|
|||||||
@@ -56,5 +56,9 @@ object GitBucketCoreModule
|
|||||||
new Version("4.24.1"),
|
new Version("4.24.1"),
|
||||||
new Version("4.25.0", new LiquibaseMigration("update/gitbucket-core_4.25.xml")),
|
new Version("4.25.0", new LiquibaseMigration("update/gitbucket-core_4.25.xml")),
|
||||||
new Version("4.26.0"),
|
new Version("4.26.0"),
|
||||||
new Version("4.27.0", new LiquibaseMigration("update/gitbucket-core_4.27.xml"))
|
new Version("4.27.0", new LiquibaseMigration("update/gitbucket-core_4.27.xml")),
|
||||||
|
new Version("4.28.0"),
|
||||||
|
new Version("4.29.0"),
|
||||||
|
new Version("4.30.0"),
|
||||||
|
new Version("4.30.1")
|
||||||
)
|
)
|
||||||
|
|||||||
9
src/main/scala/gitbucket/core/api/AddACollaborator.scala
Normal file
9
src/main/scala/gitbucket/core/api/AddACollaborator.scala
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class AddACollaborator(permission: String) {
|
||||||
|
val role: String = permission match {
|
||||||
|
case "admin" => "ADMIN"
|
||||||
|
case "push" => "DEVELOPER"
|
||||||
|
case "pull" => "GUEST"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/scala/gitbucket/core/api/ApiGroup.scala
Normal file
20
src/main/scala/gitbucket/core/api/ApiGroup.scala
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
|
||||||
|
case class ApiGroup(login: String, description: Option[String], created_at: Date) {
|
||||||
|
val id = 0 // dummy id
|
||||||
|
val url = ApiPath(s"/api/v3/orgs/${login}")
|
||||||
|
val html_url = ApiPath(s"/${login}")
|
||||||
|
val avatar_url = ApiPath(s"/${login}/_avatar")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiGroup {
|
||||||
|
def apply(group: Account): ApiGroup = ApiGroup(
|
||||||
|
login = group.userName,
|
||||||
|
description = group.description,
|
||||||
|
created_at = group.registeredDate
|
||||||
|
)
|
||||||
|
}
|
||||||
16
src/main/scala/gitbucket/core/api/CreateAPullRequest.scala
Normal file
16
src/main/scala/gitbucket/core/api/CreateAPullRequest.scala
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class CreateAPullRequest(
|
||||||
|
title: String,
|
||||||
|
head: String,
|
||||||
|
base: String,
|
||||||
|
body: Option[String],
|
||||||
|
maintainer_can_modify: Option[Boolean]
|
||||||
|
)
|
||||||
|
|
||||||
|
case class CreateAPullRequestAlt(
|
||||||
|
issue: Integer,
|
||||||
|
head: String,
|
||||||
|
base: String,
|
||||||
|
maintainer_can_modify: Option[Boolean]
|
||||||
|
)
|
||||||
11
src/main/scala/gitbucket/core/api/CreateAUser.scala
Normal file
11
src/main/scala/gitbucket/core/api/CreateAUser.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class CreateAUser(
|
||||||
|
login: String,
|
||||||
|
password: String,
|
||||||
|
email: String,
|
||||||
|
fullName: Option[String],
|
||||||
|
isAdmin: Option[Boolean],
|
||||||
|
description: Option[String],
|
||||||
|
url: Option[String]
|
||||||
|
)
|
||||||
@@ -24,6 +24,7 @@ object JsonFormat {
|
|||||||
}, { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) }
|
}, { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) }
|
||||||
)
|
)
|
||||||
) + FieldSerializer[ApiUser]() +
|
) + FieldSerializer[ApiUser]() +
|
||||||
|
FieldSerializer[ApiGroup]() +
|
||||||
FieldSerializer[ApiPullRequest]() +
|
FieldSerializer[ApiPullRequest]() +
|
||||||
FieldSerializer[ApiRepository]() +
|
FieldSerializer[ApiRepository]() +
|
||||||
FieldSerializer[ApiCommitListItem.Parent]() +
|
FieldSerializer[ApiCommitListItem.Parent]() +
|
||||||
|
|||||||
11
src/main/scala/gitbucket/core/api/UpdateAUser.scala
Normal file
11
src/main/scala/gitbucket/core/api/UpdateAUser.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class UpdateAUser(
|
||||||
|
name: Option[String],
|
||||||
|
email: Option[String],
|
||||||
|
blog: Option[String],
|
||||||
|
company: Option[String],
|
||||||
|
location: Option[String],
|
||||||
|
hireable: Option[Boolean],
|
||||||
|
bio: Option[String]
|
||||||
|
)
|
||||||
@@ -360,13 +360,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||||
// }
|
// }
|
||||||
// Remove from GROUP_MEMBER and COLLABORATOR
|
suspendAccount(account)
|
||||||
removeUserRelatedData(userName)
|
|
||||||
updateAccount(account.copy(isRemoved = true))
|
|
||||||
|
|
||||||
// call hooks
|
|
||||||
PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
|
|
||||||
|
|
||||||
session.invalidate
|
session.invalidate
|
||||||
redirect("/")
|
redirect("/")
|
||||||
}
|
}
|
||||||
@@ -427,7 +421,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
redirect(s"/${userName}/_application")
|
redirect(s"/${userName}/_application")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:userName/_hooks")(oneselfOnly {
|
get("/:userName/_hooks")(managersOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { account =>
|
getAccountByUserName(userName).map { account =>
|
||||||
gitbucket.core.account.html.hooks(account, getAccountWebHooks(account.userName), flash.get("info"))
|
gitbucket.core.account.html.hooks(account, getAccountWebHooks(account.userName), flash.get("info"))
|
||||||
@@ -437,7 +431,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Display the account web hook edit page.
|
* Display the account web hook edit page.
|
||||||
*/
|
*/
|
||||||
get("/:userName/_hooks/new")(oneselfOnly {
|
get("/:userName/_hooks/new")(managersOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { account =>
|
getAccountByUserName(userName).map { account =>
|
||||||
val webhook = AccountWebHook(userName, "", WebHookContentType.FORM, None)
|
val webhook = AccountWebHook(userName, "", WebHookContentType.FORM, None)
|
||||||
@@ -448,7 +442,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Add the account web hook URL.
|
* Add the account web hook URL.
|
||||||
*/
|
*/
|
||||||
post("/:userName/_hooks/new", accountWebHookForm(false))(oneselfOnly { form =>
|
post("/:userName/_hooks/new", accountWebHookForm(false))(managersOnly { form =>
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
addAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
addAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
||||||
flash += "info" -> s"Webhook ${form.url} created"
|
flash += "info" -> s"Webhook ${form.url} created"
|
||||||
@@ -458,7 +452,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Delete the account web hook URL.
|
* Delete the account web hook URL.
|
||||||
*/
|
*/
|
||||||
get("/:userName/_hooks/delete")(oneselfOnly {
|
get("/:userName/_hooks/delete")(managersOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
deleteAccountWebHook(userName, params("url"))
|
deleteAccountWebHook(userName, params("url"))
|
||||||
flash += "info" -> s"Webhook ${params("url")} deleted"
|
flash += "info" -> s"Webhook ${params("url")} deleted"
|
||||||
@@ -468,7 +462,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Display the account web hook edit page.
|
* Display the account web hook edit page.
|
||||||
*/
|
*/
|
||||||
get("/:userName/_hooks/edit")(oneselfOnly {
|
get("/:userName/_hooks/edit")(managersOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).flatMap { account =>
|
getAccountByUserName(userName).flatMap { account =>
|
||||||
getAccountWebHook(userName, params("url")).map {
|
getAccountWebHook(userName, params("url")).map {
|
||||||
@@ -481,7 +475,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Update account web hook settings.
|
* Update account web hook settings.
|
||||||
*/
|
*/
|
||||||
post("/:userName/_hooks/edit", accountWebHookForm(true))(oneselfOnly { form =>
|
post("/:userName/_hooks/edit", accountWebHookForm(true))(managersOnly { form =>
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
updateAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
updateAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
||||||
flash += "info" -> s"webhook ${form.url} updated"
|
flash += "info" -> s"webhook ${form.url} updated"
|
||||||
@@ -491,7 +485,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Send the test request to registered account web hook URLs.
|
* Send the test request to registered account web hook URLs.
|
||||||
*/
|
*/
|
||||||
ajaxPost("/:userName/_hooks/test")(oneselfOnly {
|
ajaxPost("/:userName/_hooks/test")(managersOnly {
|
||||||
// TODO Is it possible to merge with [[RepositorySettingsController.ajaxPost]]?
|
// TODO Is it possible to merge with [[RepositorySettingsController.ajaxPost]]?
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent._
|
import scala.concurrent._
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
import gitbucket.core.model._
|
import gitbucket.core.controller.api._
|
||||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
|
||||||
import gitbucket.core.service.PullRequestService._
|
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Directory._
|
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.JGitUtil._
|
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.servlet.Database
|
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
|
||||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
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
|
class ApiController
|
||||||
extends ApiControllerBase
|
extends ApiControllerBase
|
||||||
|
with ApiGitReferenceControllerBase
|
||||||
|
with ApiIssueCommentControllerBase
|
||||||
|
with ApiIssueControllerBase
|
||||||
|
with ApiIssueLabelControllerBase
|
||||||
|
with ApiOrganizationControllerBase
|
||||||
|
with ApiPullRequestControllerBase
|
||||||
|
with ApiRepositoryBranchControllerBase
|
||||||
|
with ApiRepositoryCollaboratorControllerBase
|
||||||
|
with ApiRepositoryCommitControllerBase
|
||||||
|
with ApiRepositoryContentsControllerBase
|
||||||
|
with ApiRepositoryControllerBase
|
||||||
|
with ApiRepositoryStatusControllerBase
|
||||||
|
with ApiUserControllerBase
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
with AccountService
|
with AccountService
|
||||||
with ProtectedBranchService
|
with ProtectedBranchService
|
||||||
@@ -36,12 +34,15 @@ class ApiController
|
|||||||
with RepositoryCreationService
|
with RepositoryCreationService
|
||||||
with IssueCreationService
|
with IssueCreationService
|
||||||
with HandleCommentService
|
with HandleCommentService
|
||||||
|
with MergeService
|
||||||
with WebHookService
|
with WebHookService
|
||||||
with WebHookPullRequestService
|
with WebHookPullRequestService
|
||||||
with WebHookIssueCommentService
|
with WebHookIssueCommentService
|
||||||
|
with WebHookPullRequestReviewCommentService
|
||||||
with WikiService
|
with WikiService
|
||||||
with ActivityService
|
with ActivityService
|
||||||
with PrioritiesService
|
with PrioritiesService
|
||||||
|
with AdminAuthenticator
|
||||||
with OwnerAuthenticator
|
with OwnerAuthenticator
|
||||||
with UsersAuthenticator
|
with UsersAuthenticator
|
||||||
with GroupManagerAuthenticator
|
with GroupManagerAuthenticator
|
||||||
@@ -50,25 +51,6 @@ class ApiController
|
|||||||
with WritableUsersAuthenticator
|
with WritableUsersAuthenticator
|
||||||
|
|
||||||
trait ApiControllerBase extends ControllerBase {
|
trait ApiControllerBase extends ControllerBase {
|
||||||
self: RepositoryService
|
|
||||||
with AccountService
|
|
||||||
with ProtectedBranchService
|
|
||||||
with IssuesService
|
|
||||||
with LabelsService
|
|
||||||
with MilestonesService
|
|
||||||
with PullRequestService
|
|
||||||
with CommitsService
|
|
||||||
with CommitStatusService
|
|
||||||
with RepositoryCreationService
|
|
||||||
with IssueCreationService
|
|
||||||
with HandleCommentService
|
|
||||||
with PrioritiesService
|
|
||||||
with OwnerAuthenticator
|
|
||||||
with UsersAuthenticator
|
|
||||||
with GroupManagerAuthenticator
|
|
||||||
with ReferrerAuthenticator
|
|
||||||
with ReadableUsersAuthenticator
|
|
||||||
with WritableUsersAuthenticator =>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 404 for non-implemented api
|
* 404 for non-implemented api
|
||||||
@@ -76,6 +58,18 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
get("/api/v3/*") {
|
get("/api/v3/*") {
|
||||||
NotFound()
|
NotFound()
|
||||||
}
|
}
|
||||||
|
post("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
put("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
delete("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
patch("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://developer.github.com/v3/#root-endpoint
|
* https://developer.github.com/v3/#root-endpoint
|
||||||
@@ -84,316 +78,6 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
JsonFormat(ApiEndPoint())
|
JsonFormat(ApiEndPoint())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/orgs/#get-an-organization
|
|
||||||
*/
|
|
||||||
get("/api/v3/orgs/:groupName") {
|
|
||||||
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/users/#get-a-single-user
|
|
||||||
* This API also returns group information (as GitHub).
|
|
||||||
*/
|
|
||||||
get("/api/v3/users/:userName") {
|
|
||||||
getAccountByUserName(params("userName")).map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/#list-organization-repositories
|
|
||||||
*/
|
|
||||||
get("/api/v3/orgs/:orgName/repos") {
|
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
|
||||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/#list-user-repositories
|
|
||||||
*/
|
|
||||||
get("/api/v3/users/:userName/repos") {
|
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
|
||||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/repos/branches/#list-branches
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
|
||||||
JsonFormat(
|
|
||||||
JGitUtil
|
|
||||||
.getBranches(
|
|
||||||
owner = repository.owner,
|
|
||||||
name = repository.name,
|
|
||||||
defaultBranch = repository.repository.defaultBranch,
|
|
||||||
origin = repository.repository.originUserName.isEmpty
|
|
||||||
)
|
|
||||||
.map { br =>
|
|
||||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
|
||||||
//import gitbucket.core.api._
|
|
||||||
(for {
|
|
||||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
|
||||||
br <- getBranches(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
repository.repository.defaultBranch,
|
|
||||||
repository.repository.originUserName.isEmpty
|
|
||||||
).find(_.name == branch)
|
|
||||||
} yield {
|
|
||||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
|
||||||
JsonFormat(
|
|
||||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
|
||||||
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
|
||||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
|
||||||
})
|
|
||||||
|
|
||||||
private def getContents(repository: RepositoryService.RepositoryInfo, path: String, refStr: String) = {
|
|
||||||
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
|
||||||
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
|
||||||
case -1 =>
|
|
||||||
(".", pathStr)
|
|
||||||
case n =>
|
|
||||||
(pathStr.take(n), pathStr.drop(n + 1))
|
|
||||||
}
|
|
||||||
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
|
||||||
}
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
|
||||||
val fileList = getFileList(git, refStr, path)
|
|
||||||
if (fileList.isEmpty) { // file or NotFound
|
|
||||||
getFileInfo(git, refStr, path)
|
|
||||||
.flatMap { f =>
|
|
||||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
|
||||||
val content = getContentFromId(git, f.id, largeFile)
|
|
||||||
request.getHeader("Accept") match {
|
|
||||||
case "application/vnd.github.v3.raw" => {
|
|
||||||
contentType = "application/vnd.github.v3.raw"
|
|
||||||
content
|
|
||||||
}
|
|
||||||
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
|
||||||
contentType = "application/vnd.github.v3.html"
|
|
||||||
content.map { c =>
|
|
||||||
List(
|
|
||||||
"<div data-path=\"",
|
|
||||||
path,
|
|
||||||
"\" id=\"file\">",
|
|
||||||
"<article>",
|
|
||||||
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
|
||||||
"</article>",
|
|
||||||
"</div>"
|
|
||||||
).mkString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "application/vnd.github.v3.html" => {
|
|
||||||
contentType = "application/vnd.github.v3.html"
|
|
||||||
content.map { c =>
|
|
||||||
List(
|
|
||||||
"<div data-path=\"",
|
|
||||||
path,
|
|
||||||
"\" id=\"file\">",
|
|
||||||
"<div class=\"plain\">",
|
|
||||||
"<pre>",
|
|
||||||
play.twirl.api.HtmlFormat.escape(new String(c)).body,
|
|
||||||
"</pre>",
|
|
||||||
"</div>",
|
|
||||||
"</div>"
|
|
||||||
).mkString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.getOrElse(NotFound())
|
|
||||||
|
|
||||||
} else { // directory
|
|
||||||
JsonFormat(fileList.map { f =>
|
|
||||||
ApiContents(f, RepositoryName(repository), None)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/git/refs/#get-a-reference
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
|
|
||||||
val revstr = multiParams("splat").head
|
|
||||||
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
|
||||||
val ref = git.getRepository().findRef(revstr)
|
|
||||||
|
|
||||||
if (ref != null) {
|
|
||||||
val sha = ref.getObjectId().name()
|
|
||||||
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
val refs = git
|
|
||||||
.getRepository()
|
|
||||||
.getAllRefs()
|
|
||||||
.asScala
|
|
||||||
.collect { case (str, ref) if str.startsWith("refs/" + revstr) => ref }
|
|
||||||
|
|
||||||
JsonFormat(refs.map { ref =>
|
|
||||||
val sha = ref.getObjectId().name()
|
|
||||||
ApiRef(revstr, ApiObject(sha))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/collaborators")(referrersOnly { repository =>
|
|
||||||
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
|
||||||
JsonFormat(
|
|
||||||
getCollaboratorUserNames(params("owner"), params("repository")).map(u => ApiUser(getAccountByUserName(u).get))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
|
||||||
*/
|
|
||||||
get("/api/v3/user") {
|
|
||||||
context.loginAccount.map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse Unauthorized()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List user's own repository
|
|
||||||
* https://developer.github.com/v3/repos/#list-your-repositories
|
|
||||||
*/
|
|
||||||
get("/api/v3/user/repos")(usersOnly {
|
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
|
||||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create user repository
|
|
||||||
* https://developer.github.com/v3/repos/#create
|
|
||||||
*/
|
|
||||||
post("/api/v3/user/repos")(usersOnly {
|
|
||||||
val owner = context.loginAccount.get.userName
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(s"${owner}/${data.name}") {
|
|
||||||
if (getRepository(owner, data.name).isEmpty) {
|
|
||||||
val f = createRepository(
|
|
||||||
context.loginAccount.get,
|
|
||||||
owner,
|
|
||||||
data.name,
|
|
||||||
data.description,
|
|
||||||
data.`private`,
|
|
||||||
data.auto_init
|
|
||||||
)
|
|
||||||
Await.result(f, Duration.Inf)
|
|
||||||
|
|
||||||
val repository = Database() withTransaction { session =>
|
|
||||||
getRepository(owner, data.name)(session).get
|
|
||||||
}
|
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
|
||||||
} else {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists on this account",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create group repository
|
|
||||||
* https://developer.github.com/v3/repos/#create
|
|
||||||
*/
|
|
||||||
post("/api/v3/orgs/:org/repos")(managersOnly {
|
|
||||||
val groupName = params("org")
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(s"${groupName}/${data.name}") {
|
|
||||||
if (getRepository(groupName, data.name).isEmpty) {
|
|
||||||
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 {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists for this group",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
|
||||||
*/
|
|
||||||
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
|
|
||||||
import gitbucket.core.api._
|
|
||||||
(for {
|
|
||||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
|
||||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
|
||||||
br <- getBranches(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
repository.repository.defaultBranch,
|
|
||||||
repository.repository.originUserName.isEmpty
|
|
||||||
).find(_.name == branch)
|
|
||||||
} yield {
|
|
||||||
if (protection.enabled) {
|
|
||||||
enableBranchProtection(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
branch,
|
|
||||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
|
||||||
protection.status.contexts
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
disableBranchProtection(repository.owner, repository.name, branch)
|
|
||||||
}
|
|
||||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
||||||
* but not enabled.
|
* but not enabled.
|
||||||
@@ -404,459 +88,6 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/issues")(referrersOnly { repository =>
|
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
// TODO: more api spec condition
|
|
||||||
val condition = IssueSearchCondition(request)
|
|
||||||
val baseOwner = getAccountByUserName(repository.owner).get
|
|
||||||
|
|
||||||
val issues: List[(Issue, Account)] =
|
|
||||||
searchIssueByApi(
|
|
||||||
condition = condition,
|
|
||||||
offset = (page - 1) * PullRequestLimit,
|
|
||||||
limit = PullRequestLimit,
|
|
||||||
repos = repository.owner -> repository.name
|
|
||||||
)
|
|
||||||
|
|
||||||
JsonFormat(issues.map {
|
|
||||||
case (issue, issueUser) =>
|
|
||||||
ApiIssue(
|
|
||||||
issue = issue,
|
|
||||||
repositoryName = RepositoryName(repository),
|
|
||||||
user = ApiUser(issueUser),
|
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository)))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/#get-a-single-issue
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
|
||||||
openedUser <- getAccountByUserName(issue.openedUserName)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(
|
|
||||||
ApiIssue(
|
|
||||||
issue,
|
|
||||||
RepositoryName(repository),
|
|
||||||
ApiUser(openedUser),
|
|
||||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/#create-an-issue
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository =>
|
|
||||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateAnIssue]
|
|
||||||
loginAccount <- context.loginAccount
|
|
||||||
} yield {
|
|
||||||
val milestone = data.milestone.flatMap(getMilestone(repository.owner, repository.name, _))
|
|
||||||
val issue = createIssue(
|
|
||||||
repository,
|
|
||||||
data.title,
|
|
||||||
data.body,
|
|
||||||
data.assignees.headOption,
|
|
||||||
milestone.map(_.milestoneId),
|
|
||||||
None,
|
|
||||||
data.labels,
|
|
||||||
loginAccount
|
|
||||||
)
|
|
||||||
JsonFormat(
|
|
||||||
ApiIssue(
|
|
||||||
issue,
|
|
||||||
RepositoryName(repository),
|
|
||||||
ApiUser(loginAccount),
|
|
||||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository)))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
} else Unauthorized()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
comments = getCommentsForApi(repository.owner, repository.name, issueId)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(comments.map {
|
|
||||||
case (issueComment, user, issue) =>
|
|
||||||
ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest)
|
|
||||||
})
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
|
||||||
body <- extractFromJsonBody[CreateAComment].map(_.body) if !body.isEmpty
|
|
||||||
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
|
||||||
(issue, id) <- handleComment(issue, Some(body), repository, action)
|
|
||||||
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
|
||||||
} yield {
|
|
||||||
JsonFormat(
|
|
||||||
ApiComment(
|
|
||||||
issueComment,
|
|
||||||
RepositoryName(repository),
|
|
||||||
issueId,
|
|
||||||
ApiUser(context.loginAccount.get),
|
|
||||||
issue.isPullRequest
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all labels for this repository
|
|
||||||
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
|
|
||||||
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
|
|
||||||
ApiLabel(label, RepositoryName(repository))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a single label
|
|
||||||
* https://developer.github.com/v3/issues/labels/#get-a-single-label
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
|
|
||||||
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
|
||||||
JsonFormat(ApiLabel(label, RepositoryName(repository)))
|
|
||||||
} getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a label
|
|
||||||
* https://developer.github.com/v3/issues/labels/#create-a-label
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
|
||||||
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
|
||||||
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
|
|
||||||
getLabel(repository.owner, repository.name, labelId).map { label =>
|
|
||||||
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
|
|
||||||
} getOrElse NotFound()
|
|
||||||
} else {
|
|
||||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
|
||||||
UnprocessableEntity(
|
|
||||||
ApiError(
|
|
||||||
"Validation Failed",
|
|
||||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a label
|
|
||||||
* https://developer.github.com/v3/issues/labels/#update-a-label
|
|
||||||
*/
|
|
||||||
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
|
||||||
} yield {
|
|
||||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
|
||||||
getLabel(repository.owner, repository.name, params("labelName")).map {
|
|
||||||
label =>
|
|
||||||
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
|
||||||
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
|
||||||
JsonFormat(
|
|
||||||
ApiLabel(
|
|
||||||
getLabel(repository.owner, repository.name, label.labelId).get,
|
|
||||||
RepositoryName(repository)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
|
||||||
UnprocessableEntity(
|
|
||||||
ApiError(
|
|
||||||
"Validation Failed",
|
|
||||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a label
|
|
||||||
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
|
||||||
*/
|
|
||||||
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
|
||||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
|
||||||
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
|
||||||
deleteLabel(repository.owner, repository.name, label.labelId)
|
|
||||||
NoContent()
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/pulls/#list-pull-requests
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
|
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
// TODO: more api spec condition
|
|
||||||
val condition = IssueSearchCondition(request)
|
|
||||||
val baseOwner = getAccountByUserName(repository.owner).get
|
|
||||||
|
|
||||||
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] =
|
|
||||||
searchPullRequestByApi(
|
|
||||||
condition = condition,
|
|
||||||
offset = (page - 1) * PullRequestLimit,
|
|
||||||
limit = PullRequestLimit,
|
|
||||||
repos = repository.owner -> repository.name
|
|
||||||
)
|
|
||||||
|
|
||||||
JsonFormat(issues.map {
|
|
||||||
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
|
|
||||||
ApiPullRequest(
|
|
||||||
issue = issue,
|
|
||||||
pullRequest = pullRequest,
|
|
||||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
|
||||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
|
||||||
user = ApiUser(issueUser),
|
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository))),
|
|
||||||
assignee = assignee.map(ApiUser.apply),
|
|
||||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
|
||||||
users = getAccountsByUserNames(
|
|
||||||
Set(repository.owner, pullRequest.requestUserName, issue.openedUserName),
|
|
||||||
Set.empty
|
|
||||||
)
|
|
||||||
baseOwner <- users.get(repository.owner)
|
|
||||||
headOwner <- users.get(pullRequest.requestUserName)
|
|
||||||
issueUser <- users.get(issue.openedUserName)
|
|
||||||
assignee = issue.assignedUserName.flatMap { userName =>
|
|
||||||
getAccountByUserName(userName, false)
|
|
||||||
}
|
|
||||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(
|
|
||||||
ApiPullRequest(
|
|
||||||
issue = issue,
|
|
||||||
pullRequest = pullRequest,
|
|
||||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
|
||||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
|
||||||
user = ApiUser(issueUser),
|
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository))),
|
|
||||||
assignee = assignee.map(ApiUser.apply),
|
|
||||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
|
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
params("id").toIntOpt.flatMap {
|
|
||||||
issueId =>
|
|
||||||
getPullRequest(owner, name, issueId) map {
|
|
||||||
case (issue, pullreq) =>
|
|
||||||
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
|
||||||
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
|
||||||
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
|
||||||
val repoFullName = RepositoryName(repository)
|
|
||||||
val commits = git.log
|
|
||||||
.addRange(oldId, newId)
|
|
||||||
.call
|
|
||||||
.iterator
|
|
||||||
.asScala
|
|
||||||
.map { c =>
|
|
||||||
ApiCommitListItem(new CommitInfo(c), repoFullName)
|
|
||||||
}
|
|
||||||
.toList
|
|
||||||
JsonFormat(commits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/#get
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repository/statuses/:sha")(writableUsersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
ref <- params.get("sha")
|
|
||||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
|
||||||
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
|
||||||
creator <- context.loginAccount
|
|
||||||
state <- CommitState.valueOf(data.state)
|
|
||||||
statusId = createCommitStatus(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
sha,
|
|
||||||
data.context.getOrElse("default"),
|
|
||||||
state,
|
|
||||||
data.target_url,
|
|
||||||
data.description,
|
|
||||||
new java.util.Date(),
|
|
||||||
creator
|
|
||||||
)
|
|
||||||
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
|
||||||
*
|
|
||||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
|
||||||
*/
|
|
||||||
val listStatusesRoute = get("/api/v3/repos/:owner/:repository/commits/:ref/statuses")(referrersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
ref <- params.get("ref")
|
|
||||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map {
|
|
||||||
case (status, creator) =>
|
|
||||||
ApiCommitStatus(status, ApiUser(creator))
|
|
||||||
})
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
|
||||||
*
|
|
||||||
* legacy route
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/statuses/:ref") {
|
|
||||||
listStatusesRoute.action()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
|
||||||
*
|
|
||||||
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/commits/:ref/status")(referrersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
ref <- params.get("ref")
|
|
||||||
owner <- getAccountByUserName(repository.owner)
|
|
||||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
|
||||||
} yield {
|
|
||||||
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
|
||||||
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/commits/#get-a-single-commit
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/commits/:sha")(referrersOnly { repository =>
|
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
val sha = params("sha")
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(owner, name))) {
|
|
||||||
git =>
|
|
||||||
val repo = git.getRepository
|
|
||||||
val objectId = repo.resolve(sha)
|
|
||||||
val commitInfo = using(new RevWalk(repo)) { revWalk =>
|
|
||||||
new CommitInfo(revWalk.parseCommit(objectId))
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonFormat(
|
|
||||||
ApiCommits(
|
|
||||||
repositoryName = RepositoryName(repository),
|
|
||||||
commitInfo = commitInfo,
|
|
||||||
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
|
||||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
|
||||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
|
||||||
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private def getAccount(userName: String, email: String): Account = {
|
|
||||||
getAccountByMailAddress(email).getOrElse {
|
|
||||||
Account(
|
|
||||||
userName = userName,
|
|
||||||
fullName = userName,
|
|
||||||
mailAddress = email,
|
|
||||||
password = "xxx",
|
|
||||||
isAdmin = false,
|
|
||||||
url = None,
|
|
||||||
registeredDate = new java.util.Date(),
|
|
||||||
updatedDate = new java.util.Date(),
|
|
||||||
lastLoginDate = None,
|
|
||||||
image = None,
|
|
||||||
isGroupAccount = false,
|
|
||||||
isRemoved = true,
|
|
||||||
description = None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
|
||||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* non-GitHub compatible API for Jenkins-Plugin
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/raw/*")(referrersOnly { repository =>
|
|
||||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
|
||||||
|
|
||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
|
||||||
responseRawFile(git, objectId, path, repository)
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* non-GitHub compatible API for listing plugins
|
* non-GitHub compatible API for listing plugins
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ class DashboardController
|
|||||||
with PullRequestService
|
with PullRequestService
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
with AccountService
|
with AccountService
|
||||||
|
with ActivityService
|
||||||
with CommitsService
|
with CommitsService
|
||||||
with LabelsService
|
with LabelsService
|
||||||
with PrioritiesService
|
with PrioritiesService
|
||||||
|
with WebHookService
|
||||||
|
with WebHookPullRequestService
|
||||||
|
with WebHookPullRequestReviewCommentService
|
||||||
with MilestonesService
|
with MilestonesService
|
||||||
with UsersAuthenticator
|
with UsersAuthenticator
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class IssuesController
|
|||||||
with WritableUsersAuthenticator
|
with WritableUsersAuthenticator
|
||||||
with PullRequestService
|
with PullRequestService
|
||||||
with WebHookIssueCommentService
|
with WebHookIssueCommentService
|
||||||
|
with WebHookPullRequestReviewCommentService
|
||||||
with CommitsService
|
with CommitsService
|
||||||
with PrioritiesService
|
with PrioritiesService
|
||||||
|
|
||||||
@@ -256,6 +257,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
"content" -> Markdown.toHtml(
|
"content" -> Markdown.toHtml(
|
||||||
markdown = x.content getOrElse "No description given.",
|
markdown = x.content getOrElse "No description given.",
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = repository.repository.defaultBranch,
|
||||||
enableWikiLink = false,
|
enableWikiLink = false,
|
||||||
enableRefsLink = true,
|
enableRefsLink = true,
|
||||||
enableAnchor = true,
|
enableAnchor = true,
|
||||||
@@ -283,6 +285,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
"content" -> view.Markdown.toHtml(
|
"content" -> view.Markdown.toHtml(
|
||||||
markdown = x.content,
|
markdown = x.content,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = repository.repository.defaultBranch,
|
||||||
enableWikiLink = false,
|
enableWikiLink = false,
|
||||||
enableRefsLink = true,
|
enableRefsLink = true,
|
||||||
enableAnchor = true,
|
enableAnchor = true,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import org.scalatra.MovedPermanently
|
import org.scalatra.MovedPermanently
|
||||||
|
|
||||||
class PreProcessController extends PreProcessControllerBase
|
class PreProcessController extends PreProcessControllerBase
|
||||||
@@ -30,7 +31,10 @@ trait PreProcessControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||||
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||||
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs")) {
|
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
|
||||||
|
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
|
||||||
|
context.currentPath.startsWith(path)
|
||||||
|
}) {
|
||||||
Unauthorized()
|
Unauthorized()
|
||||||
} else {
|
} else {
|
||||||
pass()
|
pass()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import gitbucket.core.util.Implicits._
|
|||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import org.scalatra.forms._
|
import org.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.PersonIdent
|
import org.eclipse.jgit.lib.{ObjectId, PersonIdent}
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
import org.scalatra.BadRequest
|
import org.scalatra.BadRequest
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ class PullRequestsController
|
|||||||
with CommitsService
|
with CommitsService
|
||||||
with ActivityService
|
with ActivityService
|
||||||
with WebHookPullRequestService
|
with WebHookPullRequestService
|
||||||
|
with WebHookPullRequestReviewCommentService
|
||||||
with ReadableUsersAuthenticator
|
with ReadableUsersAuthenticator
|
||||||
with ReferrerAuthenticator
|
with ReferrerAuthenticator
|
||||||
with WritableUsersAuthenticator
|
with WritableUsersAuthenticator
|
||||||
@@ -294,11 +295,12 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
issueId <- params("id").toIntOpt
|
issueId <- params("id").toIntOpt
|
||||||
loginAccount <- context.loginAccount
|
loginAccount <- context.loginAccount
|
||||||
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||||
|
repository <- getRepository(pullreq.requestUserName, pullreq.requestRepositoryName)
|
||||||
|
remoteRepository <- getRepository(pullreq.userName, pullreq.repositoryName)
|
||||||
owner = pullreq.requestUserName
|
owner = pullreq.requestUserName
|
||||||
name = pullreq.requestRepositoryName
|
name = pullreq.requestRepositoryName
|
||||||
if hasDeveloperRole(owner, name, context.loginAccount)
|
if hasDeveloperRole(owner, name, context.loginAccount)
|
||||||
} yield {
|
} yield {
|
||||||
val repository = getRepository(owner, name).get
|
|
||||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||||
if (branchProtection.needStatusCheck(loginAccount.userName)) {
|
if (branchProtection.needStatusCheck(loginAccount.userName)) {
|
||||||
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
|
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
|
||||||
@@ -314,83 +316,19 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
JGitUtil.getAllCommitIds(git)
|
JGitUtil.getAllCommitIds(git)
|
||||||
}.toSet
|
}.toSet
|
||||||
pullRemote(
|
pullRemote(
|
||||||
owner,
|
repository,
|
||||||
name,
|
|
||||||
pullreq.requestBranch,
|
pullreq.requestBranch,
|
||||||
pullreq.userName,
|
remoteRepository,
|
||||||
pullreq.repositoryName,
|
|
||||||
pullreq.branch,
|
pullreq.branch,
|
||||||
loginAccount,
|
loginAccount,
|
||||||
s"Merge branch '${alias}' into ${pullreq.requestBranch}"
|
s"Merge branch '${alias}' into ${pullreq.requestBranch}",
|
||||||
|
Some(pullreq)
|
||||||
) match {
|
) match {
|
||||||
case None => // conflict
|
case None => // conflict
|
||||||
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
|
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
|
||||||
case Some(oldId) =>
|
case Some(oldId) =>
|
||||||
// update pull request
|
// update pull request
|
||||||
updatePullRequests(owner, name, pullreq.requestBranch)
|
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize")
|
||||||
|
|
||||||
using(Git.open(Directory.getRepositoryDir(owner, name))) {
|
|
||||||
git =>
|
|
||||||
// after update branch
|
|
||||||
val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}")
|
|
||||||
val commits = git.log
|
|
||||||
.addRange(oldId, newCommitId)
|
|
||||||
.call
|
|
||||||
.iterator
|
|
||||||
.asScala
|
|
||||||
.map(c => new JGitUtil.CommitInfo(c))
|
|
||||||
.toList
|
|
||||||
|
|
||||||
commits.foreach { commit =>
|
|
||||||
if (!existIds.contains(commit.id)) {
|
|
||||||
createIssueComment(owner, name, commit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordPushActivity(owner, name, loginAccount.userName, pullreq.branch, commits)
|
|
||||||
|
|
||||||
// close issue by commit message
|
|
||||||
if (pullreq.requestBranch == repository.repository.defaultBranch) {
|
|
||||||
commits.foreach { commit =>
|
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name).foreach {
|
|
||||||
issueId =>
|
|
||||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
|
||||||
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
|
|
||||||
PluginRegistry().getIssueHooks
|
|
||||||
.foreach(
|
|
||||||
_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call web hook
|
|
||||||
callPullRequestWebHookByRequestBranch(
|
|
||||||
"synchronize",
|
|
||||||
repository,
|
|
||||||
pullreq.requestBranch,
|
|
||||||
baseUrl,
|
|
||||||
loginAccount
|
|
||||||
)
|
|
||||||
callWebHookOf(owner, name, WebHook.Push) {
|
|
||||||
for {
|
|
||||||
ownerAccount <- getAccountByUserName(owner)
|
|
||||||
} yield {
|
|
||||||
WebHookService.WebHookPushPayload(
|
|
||||||
git,
|
|
||||||
loginAccount,
|
|
||||||
pullreq.requestBranch,
|
|
||||||
repository,
|
|
||||||
commits,
|
|
||||||
ownerAccount,
|
|
||||||
oldId = oldId,
|
|
||||||
newId = newCommitId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
|
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,119 +339,14 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
|
||||||
params("id").toIntOpt.flatMap {
|
params("id").toIntOpt.flatMap { issueId =>
|
||||||
issueId =>
|
val owner = repository.owner
|
||||||
val owner = repository.owner
|
val name = repository.name
|
||||||
val name = repository.name
|
|
||||||
if (repository.repository.options.mergeOptions.split(",").contains(form.strategy)) {
|
|
||||||
LockUtil.lock(s"${owner}/${name}") {
|
|
||||||
getPullRequest(owner, name, issueId).map {
|
|
||||||
case (issue, pullreq) =>
|
|
||||||
using(Git.open(getRepositoryDir(owner, name))) {
|
|
||||||
git =>
|
|
||||||
// mark issue as merged and close.
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
|
|
||||||
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
|
|
||||||
updateClosed(owner, name, issueId, true)
|
|
||||||
|
|
||||||
// record activity
|
mergePullRequest(repository, issueId, context.loginAccount.get, form.message, form.strategy) match {
|
||||||
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
|
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||||
|
case Left(message) => Some(BadRequest())
|
||||||
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) {
|
|
||||||
commits.flatten.foreach { commit =>
|
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name).foreach {
|
|
||||||
issueId =>
|
|
||||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
|
||||||
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
|
|
||||||
PluginRegistry().getIssueHooks
|
|
||||||
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val issueContent = issue.title + " " + issue.content.getOrElse("")
|
|
||||||
closeIssuesFromMessage(
|
|
||||||
issueContent,
|
|
||||||
loginAccount.userName,
|
|
||||||
owner,
|
|
||||||
name
|
|
||||||
).foreach { issueId =>
|
|
||||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
|
||||||
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
|
|
||||||
PluginRegistry().getIssueHooks
|
|
||||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name).foreach { issueId =>
|
|
||||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
|
||||||
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
|
|
||||||
PluginRegistry().getIssueHooks
|
|
||||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePullRequests(owner, name, pullreq.branch)
|
|
||||||
|
|
||||||
// call web hook
|
|
||||||
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
|
|
||||||
|
|
||||||
// call hooks
|
|
||||||
PluginRegistry().getPullRequestHooks.foreach { h =>
|
|
||||||
h.addedComment(commentId, form.message, issue, repository)
|
|
||||||
h.merged(issue, repository)
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else Some(BadRequest())
|
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -579,97 +412,68 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
.map(_.repository.repositoryName)
|
.map(_.repository.repositoryName)
|
||||||
};
|
};
|
||||||
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
|
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
|
||||||
using(
|
val (oldId, newId) =
|
||||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
|
||||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
|
||||||
) {
|
|
||||||
case (oldGit, newGit) =>
|
|
||||||
val (oldId, newId) =
|
|
||||||
if (originRepository.branchList.contains(originId)) {
|
|
||||||
val forkedId2 =
|
|
||||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
|
||||||
|
|
||||||
val originId2 = JGitUtil.getForkedCommitId(
|
(oldId, newId) match {
|
||||||
oldGit,
|
case (Some(oldId), Some(newId)) => {
|
||||||
newGit,
|
val (commits, diffs) = getRequestCompareInfo(
|
||||||
originRepository.owner,
|
originRepository.owner,
|
||||||
originRepository.name,
|
originRepository.name,
|
||||||
originId,
|
oldId.getName,
|
||||||
forkedRepository.owner,
|
forkedRepository.owner,
|
||||||
forkedRepository.name,
|
forkedRepository.name,
|
||||||
forkedId2
|
newId.getName
|
||||||
)
|
)
|
||||||
|
|
||||||
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
val title = if (commits.flatten.length == 1) {
|
||||||
|
commits.flatten.head.shortMessage
|
||||||
} else {
|
} else {
|
||||||
val originId2 =
|
val text = forkedId.replaceAll("[\\-_]", " ")
|
||||||
originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId)
|
text.substring(0, 1).toUpperCase + text.substring(1)
|
||||||
val forkedId2 =
|
|
||||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
|
||||||
|
|
||||||
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
|
||||||
}
|
|
||||||
|
|
||||||
(oldId, newId) match {
|
|
||||||
case (Some(oldId), Some(newId)) => {
|
|
||||||
val (commits, diffs) = getRequestCompareInfo(
|
|
||||||
originRepository.owner,
|
|
||||||
originRepository.name,
|
|
||||||
oldId.getName,
|
|
||||||
forkedRepository.owner,
|
|
||||||
forkedRepository.name,
|
|
||||||
newId.getName
|
|
||||||
)
|
|
||||||
|
|
||||||
val title = if (commits.flatten.length == 1) {
|
|
||||||
commits.flatten.head.shortMessage
|
|
||||||
} else {
|
|
||||||
val text = forkedId.replaceAll("[\\-_]", " ")
|
|
||||||
text.substring(0, 1).toUpperCase + text.substring(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
html.compare(
|
|
||||||
title,
|
|
||||||
commits,
|
|
||||||
diffs,
|
|
||||||
((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
|
||||||
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,
|
|
||||||
oldId.getName,
|
|
||||||
newId.getName,
|
|
||||||
getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"),
|
|
||||||
forkedRepository,
|
|
||||||
originRepository,
|
|
||||||
forkedRepository,
|
|
||||||
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
|
|
||||||
getAssignableUserNames(originRepository.owner, originRepository.name),
|
|
||||||
getMilestones(originRepository.owner, originRepository.name),
|
|
||||||
getPriorities(originRepository.owner, originRepository.name),
|
|
||||||
getLabels(originRepository.owner, originRepository.name)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case (oldId, newId) =>
|
|
||||||
redirect(
|
|
||||||
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
|
|
||||||
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
|
|
||||||
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.compare(
|
||||||
|
title,
|
||||||
|
commits,
|
||||||
|
diffs,
|
||||||
|
((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||||
|
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, repository.defaultBranch)
|
||||||
|
},
|
||||||
|
commits.flatten
|
||||||
|
.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
||||||
|
.flatten
|
||||||
|
.toList,
|
||||||
|
originId,
|
||||||
|
forkedId,
|
||||||
|
oldId.getName,
|
||||||
|
newId.getName,
|
||||||
|
getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"),
|
||||||
|
forkedRepository,
|
||||||
|
originRepository,
|
||||||
|
forkedRepository,
|
||||||
|
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
|
||||||
|
getAssignableUserNames(originRepository.owner, originRepository.name),
|
||||||
|
getMilestones(originRepository.owner, originRepository.name),
|
||||||
|
getPriorities(originRepository.owner, originRepository.name),
|
||||||
|
getLabels(originRepository.owner, originRepository.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case (oldId, newId) =>
|
||||||
|
redirect(
|
||||||
|
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
|
||||||
|
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
|
||||||
|
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
})
|
})
|
||||||
@@ -760,7 +564,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
|
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
|
||||||
|
|
||||||
// call web hook
|
// call web hook
|
||||||
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
|
callPullRequestWebHook("opened", repository, issueId, context.loginAccount.get)
|
||||||
|
|
||||||
getIssue(owner, name, issueId.toString) foreach { issue =>
|
getIssue(owner, name, issueId.toString) foreach { issue =>
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
@@ -820,20 +624,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
html.proposals(proposedBranches, targetRepository, repository)
|
html.proposals(proposedBranches, targetRepository, repository)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses branch identifier and extracts owner and branch name as tuple.
|
|
||||||
*
|
|
||||||
* - "owner:branch" to ("owner", "branch")
|
|
||||||
* - "branch" to ("defaultOwner", "branch")
|
|
||||||
*/
|
|
||||||
private def parseCompareIdentifier(value: String, defaultOwner: String): (String, String) =
|
|
||||||
if (value.contains(':')) {
|
|
||||||
val array = value.split(":")
|
|
||||||
(array(0), array(1))
|
|
||||||
} else {
|
|
||||||
(defaultOwner, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
||||||
defining(repository.owner, repository.name) {
|
defining(repository.owner, repository.name) {
|
||||||
case (owner, repoName) =>
|
case (owner, repoName) =>
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package gitbucket.core.controller
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
import gitbucket.core.service.{AccountService, ActivityService, ReleaseService, RepositoryService}
|
import gitbucket.core.service.{AccountService, ActivityService, ReleaseService, RepositoryService}
|
||||||
import gitbucket.core.util.{FileUtil, ReadableUsersAuthenticator, ReferrerAuthenticator, WritableUsersAuthenticator}
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import org.scalatra.forms._
|
import org.scalatra.forms._
|
||||||
import gitbucket.core.releases.html
|
import gitbucket.core.releases.html
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import scala.collection.JavaConverters._
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
class ReleaseController
|
class ReleaseController
|
||||||
extends ReleaseControllerBase
|
extends ReleaseControllerBase
|
||||||
@@ -87,10 +88,12 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository =>
|
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository =>
|
||||||
val tagName = params("tag")
|
val tagName = params("tag")
|
||||||
|
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
||||||
|
|
||||||
repository.tags
|
repository.tags
|
||||||
.find(_.name == tagName)
|
.find(_.name == tagName)
|
||||||
.map { tag =>
|
.map { tag =>
|
||||||
html.form(repository, tag, None)
|
html.form(repository, tag, previousTags.map(_.name), tag.message, None)
|
||||||
}
|
}
|
||||||
.getOrElse(NotFound())
|
.getOrElse(NotFound())
|
||||||
})
|
})
|
||||||
@@ -123,14 +126,37 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
|
||||||
|
val Seq(previousTag, currentTag) = multiParams("splat")
|
||||||
|
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.getOrElse("")
|
||||||
|
|
||||||
|
val commitLog = using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse
|
||||||
|
commits
|
||||||
|
.map { commit =>
|
||||||
|
s"- ${commit.shortMessage} ${commit.id}"
|
||||||
|
}
|
||||||
|
.mkString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
commitLog
|
||||||
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository =>
|
get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository =>
|
||||||
val tagName = params("tag")
|
val tagName = params("tag")
|
||||||
|
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
||||||
|
|
||||||
(for {
|
(for {
|
||||||
release <- getRelease(repository.owner, repository.name, tagName)
|
release <- getRelease(repository.owner, repository.name, tagName)
|
||||||
tag <- repository.tags.find(_.name == tagName)
|
tag <- repository.tags.find(_.name == tagName)
|
||||||
} yield {
|
} yield {
|
||||||
html.form(repository, tag, Some(release, getReleaseAssets(repository.owner, repository.name, tagName)))
|
html.form(
|
||||||
|
repository,
|
||||||
|
tag,
|
||||||
|
previousTags.map(_.name),
|
||||||
|
release.content.getOrElse(""),
|
||||||
|
Some(release, getReleaseAssets(repository.owner, repository.name, tagName))
|
||||||
|
)
|
||||||
}).getOrElse(NotFound())
|
}).getOrElse(NotFound())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,11 @@ import gitbucket.core.util.SyntaxSugars._
|
|||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import org.scalatra.forms._
|
import org.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
import org.eclipse.jgit.lib.ObjectId
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
import gitbucket.core.model.WebHookContentType
|
import gitbucket.core.model.WebHookContentType
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
|
|
||||||
class RepositorySettingsController
|
class RepositorySettingsController
|
||||||
extends RepositorySettingsControllerBase
|
extends RepositorySettingsControllerBase
|
||||||
@@ -148,29 +146,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
if (repository.name != form.repositoryName) {
|
if (repository.name != form.repositoryName) {
|
||||||
// Update database
|
// Update database
|
||||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||||
// Move git repository
|
|
||||||
defining(getRepositoryDir(repository.owner, repository.name)) { dir =>
|
|
||||||
if (dir.isDirectory) {
|
|
||||||
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move wiki repository
|
|
||||||
defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir =>
|
|
||||||
if (dir.isDirectory) {
|
|
||||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move files directory
|
|
||||||
defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir =>
|
|
||||||
if (dir.isDirectory) {
|
|
||||||
FileUtils.moveDirectory(dir, getRepositoryFilesDir(repository.owner, form.repositoryName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Delete parent directory
|
|
||||||
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
|
|
||||||
|
|
||||||
// Call hooks
|
|
||||||
PluginRegistry().getRepositoryHooks.foreach(_.renamed(repository.owner, repository.name, form.repositoryName))
|
|
||||||
}
|
}
|
||||||
flash += "info" -> "Repository settings has been updated."
|
flash += "info" -> "Repository settings has been updated."
|
||||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||||
@@ -392,31 +367,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||||
// Change repository owner
|
// Change repository owner
|
||||||
if (repository.owner != form.newOwner) {
|
if (repository.owner != form.newOwner) {
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||||
// Update database
|
|
||||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
|
||||||
// Move git repository
|
|
||||||
defining(getRepositoryDir(repository.owner, repository.name)) { dir =>
|
|
||||||
if (dir.isDirectory) {
|
|
||||||
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move wiki repository
|
|
||||||
defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir =>
|
|
||||||
if (dir.isDirectory) {
|
|
||||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move files directory
|
|
||||||
defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir =>
|
|
||||||
if (dir.isDirectory) {
|
|
||||||
FileUtils.moveDirectory(dir, getRepositoryFilesDir(form.newOwner, repository.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call hooks
|
|
||||||
PluginRegistry().getRepositoryHooks.foreach(_.transferred(repository.owner, form.newOwner, repository.name))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
redirect(s"/${form.newOwner}/${repository.name}")
|
redirect(s"/${form.newOwner}/${repository.name}")
|
||||||
})
|
})
|
||||||
@@ -425,19 +376,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Delete the repository.
|
* Delete the repository.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
// Delete the repository and related files
|
||||||
// Delete the repository and related files
|
deleteRepository(repository.repository)
|
||||||
deleteRepository(repository.owner, repository.name)
|
|
||||||
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
|
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
|
|
||||||
FileUtils.deleteDirectory(getRepositoryFilesDir(repository.owner, repository.name))
|
|
||||||
|
|
||||||
// Call hooks
|
|
||||||
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.owner, repository.name))
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}")
|
redirect(s"/${repository.owner}")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -572,10 +512,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
private def mergeOptions = new ValueType[Seq[String]] {
|
private def mergeOptions = new ValueType[Seq[String]] {
|
||||||
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
|
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
|
||||||
params.get("mergeOptions").getOrElse(Nil)
|
params.getOrElse("mergeOptions", Nil)
|
||||||
}
|
}
|
||||||
override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = {
|
override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = {
|
||||||
val mergeOptions = params.get("mergeOptions").getOrElse(Nil)
|
val mergeOptions = params.getOrElse("mergeOptions", Nil)
|
||||||
if (mergeOptions.isEmpty) {
|
if (mergeOptions.isEmpty) {
|
||||||
Seq("mergeOptions" -> "At least one option must be enabled.")
|
Seq("mergeOptions" -> "At least one option must be enabled.")
|
||||||
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
|
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import gitbucket.core.plugin.PluginRegistry
|
|||||||
import gitbucket.core.repo.html
|
import gitbucket.core.repo.html
|
||||||
import gitbucket.core.helper
|
import gitbucket.core.helper
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.service.RepositoryCommitFileService.CommitFile
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.JGitUtil._
|
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook}
|
import gitbucket.core.model.{Account, CommitState, CommitStatus}
|
||||||
import gitbucket.core.service.WebHookService._
|
|
||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
import gitbucket.core.view.helpers
|
import gitbucket.core.view.helpers
|
||||||
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
|
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
|
||||||
@@ -24,15 +23,12 @@ import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream
|
|||||||
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
|
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
|
||||||
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
|
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
|
||||||
import org.apache.commons.compress.utils.IOUtils
|
import org.apache.commons.compress.utils.IOUtils
|
||||||
import org.scalatra.forms._
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.ec4j.core.model.PropertyType
|
import org.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
|
||||||
import org.eclipse.jgit.errors.MissingObjectException
|
import org.eclipse.jgit.errors.MissingObjectException
|
||||||
import org.eclipse.jgit.lib._
|
import org.eclipse.jgit.lib._
|
||||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk
|
import org.eclipse.jgit.treewalk.TreeWalk
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilter
|
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||||
import org.json4s.jackson.Serialization
|
import org.json4s.jackson.Serialization
|
||||||
@@ -42,6 +38,7 @@ import org.scalatra.i18n.Messages
|
|||||||
class RepositoryViewerController
|
class RepositoryViewerController
|
||||||
extends RepositoryViewerControllerBase
|
extends RepositoryViewerControllerBase
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
|
with RepositoryCommitFileService
|
||||||
with AccountService
|
with AccountService
|
||||||
with ActivityService
|
with ActivityService
|
||||||
with IssuesService
|
with IssuesService
|
||||||
@@ -64,6 +61,7 @@ class RepositoryViewerController
|
|||||||
*/
|
*/
|
||||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||||
self: RepositoryService
|
self: RepositoryService
|
||||||
|
with RepositoryCommitFileService
|
||||||
with AccountService
|
with AccountService
|
||||||
with ActivityService
|
with ActivityService
|
||||||
with IssuesService
|
with IssuesService
|
||||||
@@ -176,7 +174,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
helpers.renderMarkup(
|
helpers.renderMarkup(
|
||||||
filePath = List(f),
|
filePath = List(f),
|
||||||
fileContent = params("content"),
|
fileContent = params("content"),
|
||||||
branch = "master",
|
branch = repository.repository.defaultBranch,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||||
@@ -186,6 +184,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
helpers.markdown(
|
helpers.markdown(
|
||||||
markdown = params("content"),
|
markdown = params("content"),
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = repository.repository.defaultBranch,
|
||||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||||
@@ -319,13 +318,34 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
|
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val newFiles = files.map { file =>
|
||||||
|
file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}")
|
||||||
|
}
|
||||||
|
|
||||||
commitFiles(
|
commitFiles(
|
||||||
repository = repository,
|
repository = repository,
|
||||||
branch = form.branch,
|
branch = form.branch,
|
||||||
path = form.path,
|
path = form.path,
|
||||||
files = files,
|
files = files,
|
||||||
message = form.message.getOrElse("Add files via upload")
|
message = form.message.getOrElse("Add files via upload"),
|
||||||
)
|
loginAccount = context.loginAccount.get
|
||||||
|
) {
|
||||||
|
case (git, headTip, builder, inserter) =>
|
||||||
|
JGitUtil.processTree(git, headTip) { (path, tree) =>
|
||||||
|
if (!newFiles.exists(_.name.contains(path))) {
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newFiles.foreach { file =>
|
||||||
|
val bytes =
|
||||||
|
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
|
||||||
|
builder.add(
|
||||||
|
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
|
||||||
|
)
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (form.path.length == 0) {
|
if (form.path.length == 0) {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
|
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
|
||||||
@@ -394,7 +414,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
|
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
|
||||||
charset = form.charset,
|
charset = form.charset,
|
||||||
message = form.message.getOrElse(s"Create ${form.newFileName}"),
|
message = form.message.getOrElse(s"Create ${form.newFileName}"),
|
||||||
commit = form.commit
|
commit = form.commit,
|
||||||
|
loginAccount = context.loginAccount.get
|
||||||
)
|
)
|
||||||
|
|
||||||
redirect(
|
redirect(
|
||||||
@@ -417,7 +438,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
} else {
|
} else {
|
||||||
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
||||||
},
|
},
|
||||||
commit = form.commit
|
commit = form.commit,
|
||||||
|
loginAccount = context.loginAccount.get
|
||||||
)
|
)
|
||||||
|
|
||||||
redirect(
|
redirect(
|
||||||
@@ -436,7 +458,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
content = "",
|
content = "",
|
||||||
charset = "",
|
charset = "",
|
||||||
message = form.message.getOrElse(s"Delete ${form.fileName}"),
|
message = form.message.getOrElse(s"Delete ${form.fileName}"),
|
||||||
commit = form.commit
|
commit = form.commit,
|
||||||
|
loginAccount = context.loginAccount.get
|
||||||
)
|
)
|
||||||
|
|
||||||
println(form.path)
|
println(form.path)
|
||||||
@@ -593,50 +616,17 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
val id = params("id")
|
val id = params("id")
|
||||||
createCommitComment(
|
createCommitComment(
|
||||||
repository.owner,
|
repository,
|
||||||
repository.name,
|
|
||||||
id,
|
id,
|
||||||
context.loginAccount.get.userName,
|
context.loginAccount.get,
|
||||||
form.content,
|
form.content,
|
||||||
form.fileName,
|
form.fileName,
|
||||||
form.oldLineNumber,
|
form.oldLineNumber,
|
||||||
form.newLineNumber,
|
form.newLineNumber,
|
||||||
|
form.diff,
|
||||||
form.issueId
|
form.issueId
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
|
||||||
fileName <- form.fileName
|
|
||||||
diff <- form.diff
|
|
||||||
} {
|
|
||||||
saveCommitCommentDiff(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
id,
|
|
||||||
fileName,
|
|
||||||
form.oldLineNumber,
|
|
||||||
form.newLineNumber,
|
|
||||||
diff
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
form.issueId match {
|
|
||||||
case Some(issueId) =>
|
|
||||||
recordCommentPullRequestActivity(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
context.loginAccount.get.userName,
|
|
||||||
issueId,
|
|
||||||
form.content
|
|
||||||
)
|
|
||||||
case None =>
|
|
||||||
recordCommentCommitActivity(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
context.loginAccount.get.userName,
|
|
||||||
id,
|
|
||||||
form.content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
|
redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -661,64 +651,18 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
val id = params("id")
|
val id = params("id")
|
||||||
val commentId = createCommitComment(
|
val commentId = createCommitComment(
|
||||||
repository.owner,
|
repository,
|
||||||
repository.name,
|
|
||||||
id,
|
id,
|
||||||
context.loginAccount.get.userName,
|
context.loginAccount.get,
|
||||||
form.content,
|
form.content,
|
||||||
form.fileName,
|
form.fileName,
|
||||||
form.oldLineNumber,
|
form.oldLineNumber,
|
||||||
form.newLineNumber,
|
form.newLineNumber,
|
||||||
|
form.diff,
|
||||||
form.issueId
|
form.issueId
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
|
||||||
fileName <- form.fileName
|
|
||||||
diff <- form.diff
|
|
||||||
} {
|
|
||||||
saveCommitCommentDiff(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
id,
|
|
||||||
fileName,
|
|
||||||
form.oldLineNumber,
|
|
||||||
form.newLineNumber,
|
|
||||||
diff
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
|
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
|
||||||
form.issueId match {
|
|
||||||
case Some(issueId) =>
|
|
||||||
getPullRequest(repository.owner, repository.name, issueId).foreach {
|
|
||||||
case (issue, pullRequest) =>
|
|
||||||
recordCommentPullRequestActivity(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
context.loginAccount.get.userName,
|
|
||||||
issueId,
|
|
||||||
form.content
|
|
||||||
)
|
|
||||||
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, form.content, issue, repository))
|
|
||||||
callPullRequestReviewCommentWebHook(
|
|
||||||
"create",
|
|
||||||
comment,
|
|
||||||
repository,
|
|
||||||
issue,
|
|
||||||
pullRequest,
|
|
||||||
context.baseUrl,
|
|
||||||
context.loginAccount.get
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case None =>
|
|
||||||
recordCommentCommitActivity(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
context.loginAccount.get.userName,
|
|
||||||
id,
|
|
||||||
form.content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
helper.html
|
helper.html
|
||||||
.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
||||||
})
|
})
|
||||||
@@ -736,6 +680,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
"content" -> view.Markdown.toHtml(
|
"content" -> view.Markdown.toHtml(
|
||||||
markdown = x.content,
|
markdown = x.content,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = repository.repository.defaultBranch,
|
||||||
enableWikiLink = false,
|
enableWikiLink = false,
|
||||||
enableRefsLink = true,
|
enableRefsLink = true,
|
||||||
enableAnchor = true,
|
enableAnchor = true,
|
||||||
@@ -930,185 +875,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
lazy val isValid: Boolean = fileIds.nonEmpty
|
lazy val isValid: Boolean = fileIds.nonEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
case class CommitFile(id: String, name: String)
|
|
||||||
|
|
||||||
private def commitFiles(
|
|
||||||
repository: RepositoryService.RepositoryInfo,
|
|
||||||
files: Seq[CommitFile],
|
|
||||||
branch: String,
|
|
||||||
path: String,
|
|
||||||
message: String
|
|
||||||
) = {
|
|
||||||
// prepend path to the filename
|
|
||||||
val newFiles = files.map { file =>
|
|
||||||
file.copy(name = if (path.length == 0) file.name else s"${path}/${file.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
_commitFile(repository, branch, message) {
|
|
||||||
case (git, headTip, builder, inserter) =>
|
|
||||||
JGitUtil.processTree(git, headTip) { (path, tree) =>
|
|
||||||
if (!newFiles.exists(_.name.contains(path))) {
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newFiles.foreach { file =>
|
|
||||||
val bytes =
|
|
||||||
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
|
|
||||||
builder.add(
|
|
||||||
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
|
|
||||||
)
|
|
||||||
builder.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def commitFile(
|
|
||||||
repository: RepositoryService.RepositoryInfo,
|
|
||||||
branch: String,
|
|
||||||
path: String,
|
|
||||||
newFileName: Option[String],
|
|
||||||
oldFileName: Option[String],
|
|
||||||
content: String,
|
|
||||||
charset: String,
|
|
||||||
message: String,
|
|
||||||
commit: String
|
|
||||||
) = {
|
|
||||||
|
|
||||||
val newPath = newFileName.map { newFileName =>
|
|
||||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
|
||||||
}
|
|
||||||
val oldPath = oldFileName.map { oldFileName =>
|
|
||||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
|
||||||
}
|
|
||||||
|
|
||||||
_commitFile(repository, branch, message) {
|
|
||||||
case (git, headTip, builder, inserter) =>
|
|
||||||
if (headTip.getName == commit) {
|
|
||||||
val permission = JGitUtil
|
|
||||||
.processTree(git, headTip) { (path, tree) =>
|
|
||||||
// Add all entries except the editing file
|
|
||||||
if (!newPath.contains(path) && !oldPath.contains(path)) {
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
|
||||||
}
|
|
||||||
// Retrieve permission if file exists to keep it
|
|
||||||
oldPath.collect { case x if x == path => tree.getEntryFileMode.getBits }
|
|
||||||
}
|
|
||||||
.flatten
|
|
||||||
.headOption
|
|
||||||
|
|
||||||
newPath.foreach { newPath =>
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
|
|
||||||
FileMode.fromBits(bits)
|
|
||||||
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
|
|
||||||
}
|
|
||||||
builder.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def _commitFile(repository: RepositoryService.RepositoryInfo, branch: String, message: String)(
|
|
||||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
|
||||||
) = {
|
|
||||||
|
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
val builder = DirCache.newInCore.builder()
|
|
||||||
val inserter = git.getRepository.newObjectInserter()
|
|
||||||
val headName = s"refs/heads/${branch}"
|
|
||||||
val headTip = git.getRepository.resolve(headName)
|
|
||||||
|
|
||||||
f(git, headTip, builder, inserter)
|
|
||||||
|
|
||||||
val commitId = JGitUtil.createNewCommit(
|
|
||||||
git,
|
|
||||||
inserter,
|
|
||||||
headTip,
|
|
||||||
builder.getDirCache.writeTree(inserter),
|
|
||||||
headName,
|
|
||||||
loginAccount.fullName,
|
|
||||||
loginAccount.mailAddress,
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
inserter.flush()
|
|
||||||
inserter.close()
|
|
||||||
|
|
||||||
val receivePack = new ReceivePack(git.getRepository)
|
|
||||||
val receiveCommand = new ReceiveCommand(headTip, commitId, headName)
|
|
||||||
|
|
||||||
// call post commit hook
|
|
||||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
|
||||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
|
||||||
}.headOption
|
|
||||||
|
|
||||||
error match {
|
|
||||||
case Some(error) =>
|
|
||||||
// commit is rejected
|
|
||||||
// TODO Notify commit failure to edited user
|
|
||||||
val refUpdate = git.getRepository.updateRef(headName)
|
|
||||||
refUpdate.setNewObjectId(headTip)
|
|
||||||
refUpdate.setForceUpdate(true)
|
|
||||||
refUpdate.update()
|
|
||||||
|
|
||||||
case None =>
|
|
||||||
// update refs
|
|
||||||
val refUpdate = git.getRepository.updateRef(headName)
|
|
||||||
refUpdate.setNewObjectId(commitId)
|
|
||||||
refUpdate.setForceUpdate(false)
|
|
||||||
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
|
||||||
refUpdate.update()
|
|
||||||
|
|
||||||
// update pull request
|
|
||||||
updatePullRequests(repository.owner, repository.name, branch)
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
|
||||||
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
|
|
||||||
|
|
||||||
// create issue comment by commit message
|
|
||||||
createIssueComment(repository.owner, repository.name, commitInfo)
|
|
||||||
|
|
||||||
// close issue by commit message
|
|
||||||
if (branch == repository.repository.defaultBranch) {
|
|
||||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach {
|
|
||||||
issueId =>
|
|
||||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
|
||||||
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
|
|
||||||
PluginRegistry().getIssueHooks
|
|
||||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call post commit hook
|
|
||||||
PluginRegistry().getReceiveHooks.foreach { hook =>
|
|
||||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
|
||||||
}
|
|
||||||
|
|
||||||
//call web hook
|
|
||||||
callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount)
|
|
||||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
|
||||||
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
|
|
||||||
getAccountByUserName(repository.owner).map { ownerAccount =>
|
|
||||||
WebHookPushPayload(
|
|
||||||
git,
|
|
||||||
loginAccount,
|
|
||||||
headName,
|
|
||||||
repository,
|
|
||||||
List(commit),
|
|
||||||
ownerAccount,
|
|
||||||
oldId = headTip,
|
|
||||||
newId = commitId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
|
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
|
||||||
s"readme.${extension}"
|
s"readme.${extension}"
|
||||||
} ++ Seq("readme.txt", "readme")
|
} ++ Seq("readme.txt", "readme")
|
||||||
@@ -1179,18 +945,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
path: String
|
path: String
|
||||||
) = {
|
) = {
|
||||||
def archive(revision: String, archiveFormat: String, archive: ArchiveOutputStream)(
|
def archive(revision: String, archiveFormat: String, archive: ArchiveOutputStream)(
|
||||||
entryCreator: (String, Long, Int) => ArchiveEntry
|
entryCreator: (String, Long, java.util.Date, Int) => ArchiveEntry
|
||||||
): Unit = {
|
): Unit = {
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
val oid = git.getRepository.resolve(revision)
|
val oid = git.getRepository.resolve(revision)
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, oid)
|
val commit = JGitUtil.getRevCommitFromId(git, oid)
|
||||||
|
val date = commit.getCommitterIdent.getWhen
|
||||||
val sha1 = oid.getName()
|
val sha1 = oid.getName()
|
||||||
val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-')
|
val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-')
|
||||||
val pathSuffix = if (path.isEmpty) "" else '-' + path.replace('/', '-')
|
val pathSuffix = if (path.isEmpty) "" else '-' + path.replace('/', '-')
|
||||||
val baseName = repository.name + "-" + repositorySuffix + pathSuffix
|
val baseName = repository.name + "-" + repositorySuffix + pathSuffix
|
||||||
|
|
||||||
using(new TreeWalk(git.getRepository)) { treeWalk =>
|
using(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||||
treeWalk.addTree(revCommit.getTree)
|
treeWalk.addTree(commit.getTree)
|
||||||
treeWalk.setRecursive(true)
|
treeWalk.setRecursive(true)
|
||||||
if (!path.isEmpty) {
|
if (!path.isEmpty) {
|
||||||
treeWalk.setFilter(PathFilter.create(path))
|
treeWalk.setFilter(PathFilter.create(path))
|
||||||
@@ -1202,8 +969,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
else path.split("/").last + treeWalk.getPathString.substring(path.length)
|
else path.split("/").last + treeWalk.getPathString.substring(path.length)
|
||||||
val size = JGitUtil.getFileSize(git, repository, treeWalk)
|
val size = JGitUtil.getFileSize(git, repository, treeWalk)
|
||||||
val mode = treeWalk.getFileMode.getBits
|
val mode = treeWalk.getFileMode.getBits
|
||||||
val entry: ArchiveEntry = entryCreator(entryPath, size, mode)
|
val entry: ArchiveEntry = entryCreator(entryPath, size, date, mode)
|
||||||
JGitUtil.openFile(git, repository, revCommit.getTree, treeWalk.getPathString) { in =>
|
JGitUtil.openFile(git, repository, commit.getTree, treeWalk.getPathString) { in =>
|
||||||
archive.putArchiveEntry(entry)
|
archive.putArchiveEntry(entry)
|
||||||
IOUtils.copy(in, archive)
|
IOUtils.copy(in, archive)
|
||||||
archive.closeArchiveEntry()
|
archive.closeArchiveEntry()
|
||||||
@@ -1228,10 +995,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
contentType = "application/octet-stream"
|
contentType = "application/octet-stream"
|
||||||
response.setBufferSize(1024 * 1024)
|
response.setBufferSize(1024 * 1024)
|
||||||
using(new ZipArchiveOutputStream(response.getOutputStream)) { zip =>
|
using(new ZipArchiveOutputStream(response.getOutputStream)) { zip =>
|
||||||
archive(revision, ".zip", zip) { (path, size, mode) =>
|
archive(revision, ".zip", zip) { (path, size, date, mode) =>
|
||||||
val entry = new ZipArchiveEntry(path)
|
val entry = new ZipArchiveEntry(path)
|
||||||
entry.setSize(size)
|
entry.setSize(size)
|
||||||
entry.setUnixMode(mode)
|
entry.setUnixMode(mode)
|
||||||
|
entry.setTime(date.getTime)
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1252,9 +1020,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
|
tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
|
||||||
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
|
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
|
||||||
tar.setAddPaxHeadersForNonAsciiNames(true)
|
tar.setAddPaxHeadersForNonAsciiNames(true)
|
||||||
archive(revision, ".tar.gz", tar) { (path, size, mode) =>
|
archive(revision, ".tar.gz", tar) { (path, size, date, mode) =>
|
||||||
val entry = new TarArchiveEntry(path)
|
val entry = new TarArchiveEntry(path)
|
||||||
entry.setSize(size)
|
entry.setSize(size)
|
||||||
|
entry.setModTime(date)
|
||||||
entry.setMode(mode)
|
entry.setMode(mode)
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,9 +94,16 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
),
|
),
|
||||||
"skinName" -> trim(label("AdminLTE skin name", text(required))),
|
"skinName" -> trim(label("AdminLTE skin name", text(required))),
|
||||||
"showMailAddress" -> trim(label("Show mail address", boolean())),
|
"showMailAddress" -> trim(label("Show mail address", boolean())),
|
||||||
"pluginNetworkInstall" -> new SingleValueType[Boolean] {
|
"pluginNetworkInstall" -> trim(label("Network plugin installation", boolean())),
|
||||||
override def convert(value: String, messages: Messages): Boolean = context.settings.pluginNetworkInstall
|
"proxy" -> optionalIfNotChecked(
|
||||||
}
|
"useProxy",
|
||||||
|
mapping(
|
||||||
|
"host" -> trim(label("Proxy host", text(required))),
|
||||||
|
"port" -> trim(label("Proxy port", number())),
|
||||||
|
"user" -> trim(label("Keystore", optional(text()))),
|
||||||
|
"password" -> trim(label("Keystore", optional(text())))
|
||||||
|
)(Proxy.apply)
|
||||||
|
)
|
||||||
)(SystemSettings.apply).verifying { settings =>
|
)(SystemSettings.apply).verifying { settings =>
|
||||||
Vector(
|
Vector(
|
||||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||||
@@ -380,11 +387,6 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/plugins/_reload")(adminOnly {
|
post("/admin/plugins/_reload")(adminOnly {
|
||||||
// Update configuration
|
|
||||||
val pluginNetworkInstall = params.get("pluginNetworkInstall").map(_.toBoolean).getOrElse(false)
|
|
||||||
saveSystemSettings(context.settings.copy(pluginNetworkInstall = pluginNetworkInstall))
|
|
||||||
|
|
||||||
// Reload plugins
|
|
||||||
PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn)
|
PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn)
|
||||||
flash += "info" -> "All plugins were reloaded."
|
flash += "info" -> "All plugins were reloaded."
|
||||||
redirect("/admin/plugins")
|
redirect("/admin/plugins")
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiObject, ApiRef, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.ReferrerAuthenticator
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||||
|
self: ReferrerAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Get a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#get-a-reference
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
|
||||||
|
val revstr = multiParams("splat").head
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
|
val ref = git.getRepository().findRef(revstr)
|
||||||
|
|
||||||
|
if (ref != null) {
|
||||||
|
val sha = ref.getObjectId().name()
|
||||||
|
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val refs = git
|
||||||
|
.getRepository()
|
||||||
|
.getRefDatabase()
|
||||||
|
.getRefsByPrefix("refs/")
|
||||||
|
.asScala
|
||||||
|
|
||||||
|
JsonFormat(refs.map { ref =>
|
||||||
|
val sha = ref.getObjectId().name()
|
||||||
|
ApiRef(revstr, ApiObject(sha))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* ii. Get all references
|
||||||
|
* https://developer.github.com/v3/git/refs/#get-all-references
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Create a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#create-a-reference
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#update-a-reference
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Delete a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#delete-a-reference
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiComment, ApiUser, CreateAComment, JsonFormat}
|
||||||
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||||
|
|
||||||
|
trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||||
|
self: AccountService
|
||||||
|
with IssuesService
|
||||||
|
with RepositoryService
|
||||||
|
with HandleCommentService
|
||||||
|
with MilestonesService
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with ReferrerAuthenticator =>
|
||||||
|
/*
|
||||||
|
* i. List comments on an issue
|
||||||
|
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
comments = getCommentsForApi(repository.owner, repository.name, issueId)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(comments.map {
|
||||||
|
case (issueComment, user, issue) =>
|
||||||
|
ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest)
|
||||||
|
})
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List comments in a repository
|
||||||
|
* https://developer.github.com/v3/issues/comments/#list-comments-in-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get a single comment
|
||||||
|
* https://developer.github.com/v3/issues/comments/#get-a-single-comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Create a comment
|
||||||
|
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||||
|
body <- extractFromJsonBody[CreateAComment].map(_.body) if !body.isEmpty
|
||||||
|
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||||
|
(issue, id) <- handleComment(issue, Some(body), repository, action)
|
||||||
|
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
||||||
|
} yield {
|
||||||
|
JsonFormat(
|
||||||
|
ApiComment(
|
||||||
|
issueComment,
|
||||||
|
RepositoryName(repository),
|
||||||
|
issueId,
|
||||||
|
ApiUser(context.loginAccount.get),
|
||||||
|
issue.isPullRequest
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Edit a comment
|
||||||
|
* https://developer.github.com/v3/issues/comments/#edit-a-comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Delete a comment
|
||||||
|
* https://developer.github.com/v3/issues/comments/#delete-a-comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||||
|
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.{Account, Issue}
|
||||||
|
import gitbucket.core.service.{AccountService, IssueCreationService, IssuesService, MilestonesService}
|
||||||
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
|
import gitbucket.core.service.PullRequestService.PullRequestLimit
|
||||||
|
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName, UsersAuthenticator}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
|
||||||
|
trait ApiIssueControllerBase extends ControllerBase {
|
||||||
|
self: AccountService
|
||||||
|
with IssuesService
|
||||||
|
with IssueCreationService
|
||||||
|
with MilestonesService
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with ReferrerAuthenticator =>
|
||||||
|
/*
|
||||||
|
* i. List issues
|
||||||
|
* https://developer.github.com/v3/issues/#list-issues
|
||||||
|
* requested: 1743
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List issues for a repository
|
||||||
|
* https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues")(referrersOnly { repository =>
|
||||||
|
val page = IssueSearchCondition.page(request)
|
||||||
|
// TODO: more api spec condition
|
||||||
|
val condition = IssueSearchCondition(request)
|
||||||
|
val baseOwner = getAccountByUserName(repository.owner).get
|
||||||
|
|
||||||
|
val issues: List[(Issue, Account)] =
|
||||||
|
searchIssueByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map {
|
||||||
|
case (issue, issueUser) =>
|
||||||
|
ApiIssue(
|
||||||
|
issue = issue,
|
||||||
|
repositoryName = RepositoryName(repository),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository)))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get a single issue
|
||||||
|
* https://developer.github.com/v3/issues/#get-a-single-issue
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||||
|
openedUser <- getAccountByUserName(issue.openedUserName)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(
|
||||||
|
ApiIssue(
|
||||||
|
issue,
|
||||||
|
RepositoryName(repository),
|
||||||
|
ApiUser(openedUser),
|
||||||
|
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Create an issue
|
||||||
|
* https://developer.github.com/v3/issues/#create-an-issue
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository =>
|
||||||
|
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateAnIssue]
|
||||||
|
loginAccount <- context.loginAccount
|
||||||
|
} yield {
|
||||||
|
val milestone = data.milestone.flatMap(getMilestone(repository.owner, repository.name, _))
|
||||||
|
val issue = createIssue(
|
||||||
|
repository,
|
||||||
|
data.title,
|
||||||
|
data.body,
|
||||||
|
data.assignees.headOption,
|
||||||
|
milestone.map(_.milestoneId),
|
||||||
|
None,
|
||||||
|
data.labels,
|
||||||
|
loginAccount
|
||||||
|
)
|
||||||
|
JsonFormat(
|
||||||
|
ApiIssue(
|
||||||
|
issue,
|
||||||
|
RepositoryName(repository),
|
||||||
|
ApiUser(loginAccount),
|
||||||
|
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
} else Unauthorized()
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* v. Edit an issue
|
||||||
|
* https://developer.github.com/v3/issues/#edit-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Lock an issue
|
||||||
|
* https://developer.github.com/v3/issues/#lock-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Unlock an issue
|
||||||
|
* https://developer.github.com/v3/issues/#unlock-an-issue
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiError, ApiLabel, CreateALabel, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
||||||
|
|
||||||
|
trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||||
|
self: AccountService
|
||||||
|
with IssuesService
|
||||||
|
with LabelsService
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. List all labels for this repository
|
||||||
|
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
|
||||||
|
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
|
||||||
|
ApiLabel(label, RepositoryName(repository))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. Get a single label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#get-a-single-label
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
JsonFormat(ApiLabel(label, RepositoryName(repository)))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Create a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
|
||||||
|
getLabel(repository.owner, repository.name, labelId).map { label =>
|
||||||
|
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(
|
||||||
|
ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map {
|
||||||
|
label =>
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||||
|
JsonFormat(
|
||||||
|
ApiLabel(
|
||||||
|
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||||
|
RepositoryName(repository)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(
|
||||||
|
ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Delete a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
deleteLabel(repository.owner, repository.name, label.labelId)
|
||||||
|
NoContent()
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* vi. List labels on an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#list-labels-on-an-issue
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues/:id/labels")(referrersOnly { repository =>
|
||||||
|
JsonFormat(getIssueLabels(repository.owner, repository.name, params("id").toInt).map { l =>
|
||||||
|
ApiLabel(l, RepositoryName(repository.owner, repository.name))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Add labels to an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||||
|
JsonFormat(for {
|
||||||
|
data <- extractFromJsonBody[Seq[String]];
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
} yield {
|
||||||
|
data.map { labelName =>
|
||||||
|
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
||||||
|
getLabel(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
createLabel(repository.owner, repository.name, labelName)
|
||||||
|
).get
|
||||||
|
)
|
||||||
|
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId, true)
|
||||||
|
ApiLabel(label, RepositoryName(repository.owner, repository.name))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. Remove a label from an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/issues/:id/labels/:name")(writableUsersOnly { repository =>
|
||||||
|
val issueId = params("id").toInt
|
||||||
|
val labelName = params("name")
|
||||||
|
getLabel(repository.owner, repository.name, labelName) match {
|
||||||
|
case Some(label) =>
|
||||||
|
deleteIssueLabel(repository.owner, repository.name, issueId, label.labelId, true)
|
||||||
|
JsonFormat(Seq(label))
|
||||||
|
case None =>
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. Replace all labels for an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue
|
||||||
|
*/
|
||||||
|
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||||
|
JsonFormat(for {
|
||||||
|
data <- extractFromJsonBody[Seq[String]];
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
} yield {
|
||||||
|
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
||||||
|
data.map { labelName =>
|
||||||
|
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
||||||
|
getLabel(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
createLabel(repository.owner, repository.name, labelName)
|
||||||
|
).get
|
||||||
|
)
|
||||||
|
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId, true)
|
||||||
|
ApiLabel(label, RepositoryName(repository.owner, repository.name))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. Remove all labels from an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||||
|
val issueId = params("id").toInt
|
||||||
|
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
||||||
|
NoContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xi Get labels for every issue in a milestone
|
||||||
|
* https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiGroup, ApiRepository, ApiUser, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.UsersAuthenticator
|
||||||
|
|
||||||
|
trait ApiOrganizationControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with AccountService with UsersAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. List your organizations
|
||||||
|
* https://developer.github.com/v3/orgs/#list-your-organizations
|
||||||
|
*/
|
||||||
|
get("/api/v3/user/orgs")(usersOnly {
|
||||||
|
JsonFormat(getGroupsByUserName(context.loginAccount.get.userName).flatMap(getAccountByUserName(_)).map(ApiGroup(_)))
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List all organizations
|
||||||
|
* https://developer.github.com/v3/orgs/#list-all-organizations
|
||||||
|
*/
|
||||||
|
get("/api/v3/organizations") {
|
||||||
|
JsonFormat(getAllUsers(false, true).filter(a => a.isGroupAccount).map(ApiGroup(_)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. List user organizations
|
||||||
|
* https://developer.github.com/v3/orgs/#list-user-organizations
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName/orgs") {
|
||||||
|
JsonFormat(getGroupsByUserName(params("userName")).flatMap(getAccountByUserName(_)).map(ApiGroup(_)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iv. Get an organization
|
||||||
|
* https://developer.github.com/v3/orgs/#get-an-organization
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:groupName") {
|
||||||
|
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
|
||||||
|
JsonFormat(ApiGroup(account))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Edit an organization
|
||||||
|
* https://developer.github.com/v3/orgs/#edit-an-organization
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ghe: i. Create an organization
|
||||||
|
* https://developer.github.com/enterprise/2.14/v3/enterprise-admin/orgs/#create-an-organization
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ghe: ii. Rename an organization
|
||||||
|
* https://developer.github.com/enterprise/2.14/v3/enterprise-admin/orgs/#rename-an-organization
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* should implement delete an organization API?
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.{Account, Issue, PullRequest, Repository}
|
||||||
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
|
import gitbucket.core.service.PullRequestService.PullRequestLimit
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.scalatra.NoContent
|
||||||
|
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
trait ApiPullRequestControllerBase extends ControllerBase {
|
||||||
|
self: AccountService
|
||||||
|
with IssuesService
|
||||||
|
with PullRequestService
|
||||||
|
with RepositoryService
|
||||||
|
with MergeService
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Link Relations
|
||||||
|
* https://developer.github.com/v3/pulls/#link-relations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List pull requests
|
||||||
|
* https://developer.github.com/v3/pulls/#list-pull-requests
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||||
|
val page = IssueSearchCondition.page(request)
|
||||||
|
// TODO: more api spec condition
|
||||||
|
val condition = IssueSearchCondition(request)
|
||||||
|
val baseOwner = getAccountByUserName(repository.owner).get
|
||||||
|
|
||||||
|
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] =
|
||||||
|
searchPullRequestByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map {
|
||||||
|
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
|
||||||
|
ApiPullRequest(
|
||||||
|
issue = issue,
|
||||||
|
pullRequest = pullRequest,
|
||||||
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get a single pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
} yield {
|
||||||
|
JsonFormat(getApiPullRequest(repository, issueId))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Create a pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#create-a-pull-request
|
||||||
|
* requested #1843
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/pulls")(readableUsersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[Either[CreateAPullRequest, CreateAPullRequestAlt]]
|
||||||
|
} yield {
|
||||||
|
data match {
|
||||||
|
case Left(createPullReq) =>
|
||||||
|
val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReq.head, repository.owner)
|
||||||
|
getRepository(reqOwner, repository.name)
|
||||||
|
.flatMap {
|
||||||
|
forkedRepository =>
|
||||||
|
getPullRequestCommitFromTo(repository, forkedRepository, createPullReq.base, reqBranch) match {
|
||||||
|
case (Some(commitIdFrom), Some(commitIdTo)) =>
|
||||||
|
val issueId = insertIssue(
|
||||||
|
owner = repository.owner,
|
||||||
|
repository = repository.name,
|
||||||
|
loginUser = context.loginAccount.get.userName,
|
||||||
|
title = createPullReq.title,
|
||||||
|
content = createPullReq.body,
|
||||||
|
assignedUserName = None,
|
||||||
|
milestoneId = None,
|
||||||
|
priorityId = None,
|
||||||
|
isPullRequest = true
|
||||||
|
)
|
||||||
|
|
||||||
|
createPullRequest(
|
||||||
|
originUserName = repository.owner,
|
||||||
|
originRepositoryName = repository.name,
|
||||||
|
issueId = issueId,
|
||||||
|
originBranch = createPullReq.base,
|
||||||
|
requestUserName = reqOwner,
|
||||||
|
requestRepositoryName = repository.name,
|
||||||
|
requestBranch = reqBranch,
|
||||||
|
commitIdFrom = commitIdFrom.getName,
|
||||||
|
commitIdTo = commitIdTo.getName
|
||||||
|
)
|
||||||
|
getApiPullRequest(repository, issueId).map(JsonFormat(_))
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
case Right(createPullReqAlt) =>
|
||||||
|
val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReqAlt.head, repository.owner)
|
||||||
|
getRepository(reqOwner, repository.name)
|
||||||
|
.flatMap {
|
||||||
|
forkedRepository =>
|
||||||
|
getPullRequestCommitFromTo(repository, forkedRepository, createPullReqAlt.base, reqBranch) match {
|
||||||
|
case (Some(commitIdFrom), Some(commitIdTo)) =>
|
||||||
|
changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue)
|
||||||
|
createPullRequest(
|
||||||
|
originUserName = repository.owner,
|
||||||
|
originRepositoryName = repository.name,
|
||||||
|
issueId = createPullReqAlt.issue,
|
||||||
|
originBranch = createPullReqAlt.base,
|
||||||
|
requestUserName = reqOwner,
|
||||||
|
requestRepositoryName = repository.name,
|
||||||
|
requestBranch = reqBranch,
|
||||||
|
commitIdFrom = commitIdFrom.getName,
|
||||||
|
commitIdTo = commitIdTo.getName
|
||||||
|
)
|
||||||
|
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.getOrElse {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Update a pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#update-a-pull-request
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. List commits on a pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
|
||||||
|
val owner = repository.owner
|
||||||
|
val name = repository.name
|
||||||
|
params("id").toIntOpt.flatMap {
|
||||||
|
issueId =>
|
||||||
|
getPullRequest(owner, name, issueId) map {
|
||||||
|
case (issue, pullreq) =>
|
||||||
|
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||||
|
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
||||||
|
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
||||||
|
val repoFullName = RepositoryName(repository)
|
||||||
|
val commits = git.log
|
||||||
|
.addRange(oldId, newId)
|
||||||
|
.call
|
||||||
|
.iterator
|
||||||
|
.asScala
|
||||||
|
.map { c =>
|
||||||
|
ApiCommitListItem(new CommitInfo(c), repoFullName)
|
||||||
|
}
|
||||||
|
.toList
|
||||||
|
JsonFormat(commits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* vii. List pull requests files
|
||||||
|
* https://developer.github.com/v3/pulls/#list-pull-requests-files
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. Get if a pull request has been merged
|
||||||
|
* https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/pulls/:id/merge")(referrersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
(issue, pullReq) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||||
|
} yield {
|
||||||
|
if (checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) {
|
||||||
|
NoContent
|
||||||
|
} else {
|
||||||
|
NotFound
|
||||||
|
}
|
||||||
|
}).getOrElse(NotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. Merge a pull request (Merge Button)
|
||||||
|
* https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. Labels, assignees, and milestones
|
||||||
|
* https://developer.github.com/v3/pulls/#labels-assignees-and-milestones
|
||||||
|
*/
|
||||||
|
|
||||||
|
private def getApiPullRequest(repository: RepositoryService.RepositoryInfo, issueId: Int): Option[ApiPullRequest] = {
|
||||||
|
for {
|
||||||
|
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||||
|
users = getAccountsByUserNames(
|
||||||
|
Set(repository.owner, pullRequest.requestUserName, issue.openedUserName),
|
||||||
|
Set.empty
|
||||||
|
)
|
||||||
|
baseOwner <- users.get(repository.owner)
|
||||||
|
headOwner <- users.get(pullRequest.requestUserName)
|
||||||
|
issueUser <- users.get(issue.openedUserName)
|
||||||
|
assignee = issue.assignedUserName.flatMap { userName =>
|
||||||
|
getAccountByUserName(userName, false)
|
||||||
|
}
|
||||||
|
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||||
|
} yield {
|
||||||
|
ApiPullRequest(
|
||||||
|
issue = issue,
|
||||||
|
pullRequest = pullRequest,
|
||||||
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.JGitUtil.getBranches
|
||||||
|
|
||||||
|
trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ProtectedBranchService
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* i. List branches
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
||||||
|
JsonFormat(
|
||||||
|
JGitUtil
|
||||||
|
.getBranches(
|
||||||
|
owner = repository.owner,
|
||||||
|
name = repository.name,
|
||||||
|
defaultBranch = repository.repository.defaultBranch,
|
||||||
|
origin = repository.repository.originUserName.isEmpty
|
||||||
|
)
|
||||||
|
.map { br =>
|
||||||
|
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
||||||
|
//import gitbucket.core.api._
|
||||||
|
(for {
|
||||||
|
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||||
|
br <- getBranches(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
repository.repository.defaultBranch,
|
||||||
|
repository.repository.originUserName.isEmpty
|
||||||
|
).find(_.name == branch)
|
||||||
|
} yield {
|
||||||
|
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||||
|
JsonFormat(
|
||||||
|
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get branch protection
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-branch-protection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update branch protection
|
||||||
|
* https://developer.github.com/v3/repos/branches/#update-branch-protection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Remove branch protection
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-branch-protection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Get required status checks of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-required-status-checks-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Update required status checks of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#update-required-status-checks-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. Remove required status checks of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. List required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. Replace required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#replace-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xi. Add required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xii. Remove required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiii. Get pull request review enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-pull-request-review-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiv. Update pull request review enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xv. Remove pull request review enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-pull-request-review-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xvi. Get required signatures of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-required-signatures-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xvii. Add required signatures of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-required-signatures-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xviii. Remove required signatures of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-signatures-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xix. Get admin enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-admin-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xx. Add admin enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-admin-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxi. Remove admin enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-admin-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxii. Get restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxiii. Remove restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxiv. List team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxv. Replace team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#replace-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxvi. Add team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxvii. Remove team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxviii. List user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxix. Replace user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#replace-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxx. Add user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxxi. Remove user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enabling and disabling branch protection: deprecated?
|
||||||
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
|
||||||
|
import gitbucket.core.api._
|
||||||
|
(for {
|
||||||
|
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||||
|
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||||
|
br <- getBranches(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
repository.repository.defaultBranch,
|
||||||
|
repository.repository.originUserName.isEmpty
|
||||||
|
).find(_.name == branch)
|
||||||
|
} yield {
|
||||||
|
if (protection.enabled) {
|
||||||
|
enableBranchProtection(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
branch,
|
||||||
|
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||||
|
protection.status.contexts
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
disableBranchProtection(repository.owner, repository.name, branch)
|
||||||
|
}
|
||||||
|
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{AddACollaborator, ApiUser, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.{OwnerAuthenticator, ReferrerAuthenticator}
|
||||||
|
import org.scalatra.NoContent
|
||||||
|
|
||||||
|
trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with AccountService with ReferrerAuthenticator with OwnerAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. List collaborators
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/collaborators")(referrersOnly { repository =>
|
||||||
|
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
||||||
|
JsonFormat(
|
||||||
|
getCollaboratorUserNames(params("owner"), params("repository")).map(u => ApiUser(getAccountByUserName(u).get))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* ii. Check if a user is a collaborator
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Review a user's permission level
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Add user as a collaborator
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator
|
||||||
|
* requested #1586
|
||||||
|
*/
|
||||||
|
put("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository =>
|
||||||
|
for {
|
||||||
|
data <- extractFromJsonBody[AddACollaborator]
|
||||||
|
} yield {
|
||||||
|
addCollaborator(repository.owner, repository.name, params("userName"), data.role)
|
||||||
|
NoContent()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Remove user as a collaborator
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator
|
||||||
|
* requested #1586
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository =>
|
||||||
|
removeCollaborator(repository.owner, repository.name, params("userName"))
|
||||||
|
NoContent()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiCommits, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
import gitbucket.core.service.{AccountService, CommitsService}
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
|
|
||||||
|
trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||||
|
self: AccountService with CommitsService with ReferrerAuthenticator =>
|
||||||
|
/*
|
||||||
|
* i. List commits on a repository
|
||||||
|
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. Get a single commit
|
||||||
|
* https://developer.github.com/v3/repos/commits/#get-a-single-commit
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/commits/:sha")(referrersOnly { repository =>
|
||||||
|
val owner = repository.owner
|
||||||
|
val name = repository.name
|
||||||
|
val sha = params("sha")
|
||||||
|
|
||||||
|
using(Git.open(getRepositoryDir(owner, name))) {
|
||||||
|
git =>
|
||||||
|
val repo = git.getRepository
|
||||||
|
val objectId = repo.resolve(sha)
|
||||||
|
val commitInfo = using(new RevWalk(repo)) { revWalk =>
|
||||||
|
new CommitInfo(revWalk.parseCommit(objectId))
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonFormat(
|
||||||
|
ApiCommits(
|
||||||
|
repositoryName = RepositoryName(repository),
|
||||||
|
commitInfo = commitInfo,
|
||||||
|
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
||||||
|
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||||
|
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||||
|
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private def getAccount(userName: String, email: String): Account = {
|
||||||
|
getAccountByMailAddress(email).getOrElse {
|
||||||
|
Account(
|
||||||
|
userName = userName,
|
||||||
|
fullName = userName,
|
||||||
|
mailAddress = email,
|
||||||
|
password = "xxx",
|
||||||
|
isAdmin = false,
|
||||||
|
url = None,
|
||||||
|
registeredDate = new java.util.Date(),
|
||||||
|
updatedDate = new java.util.Date(),
|
||||||
|
lastLoginDate = None,
|
||||||
|
image = None,
|
||||||
|
isGroupAccount = false,
|
||||||
|
isRemoved = true,
|
||||||
|
description = None
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get the SHA-1 of a commit reference
|
||||||
|
* https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Compare two commits
|
||||||
|
* https://developer.github.com/v3/repos/commits/#compare-two-commits
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Commit signature verification
|
||||||
|
* https://developer.github.com/v3/repos/commits/#commit-signature-verification
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiContents, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
|
trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||||
|
self: ReferrerAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Get the README
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-the-readme
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get contents
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
||||||
|
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get contents
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
||||||
|
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||||
|
})
|
||||||
|
|
||||||
|
private def getContents(repository: RepositoryService.RepositoryInfo, path: String, refStr: String) = {
|
||||||
|
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
||||||
|
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
||||||
|
case -1 =>
|
||||||
|
(".", pathStr)
|
||||||
|
case n =>
|
||||||
|
(pathStr.take(n), pathStr.drop(n + 1))
|
||||||
|
}
|
||||||
|
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
||||||
|
}
|
||||||
|
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
|
val fileList = getFileList(git, refStr, path)
|
||||||
|
if (fileList.isEmpty) { // file or NotFound
|
||||||
|
getFileInfo(git, refStr, path)
|
||||||
|
.flatMap { f =>
|
||||||
|
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||||
|
val content = getContentFromId(git, f.id, largeFile)
|
||||||
|
request.getHeader("Accept") match {
|
||||||
|
case "application/vnd.github.v3.raw" => {
|
||||||
|
contentType = "application/vnd.github.v3.raw"
|
||||||
|
content
|
||||||
|
}
|
||||||
|
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
||||||
|
contentType = "application/vnd.github.v3.html"
|
||||||
|
content.map { c =>
|
||||||
|
List(
|
||||||
|
"<div data-path=\"",
|
||||||
|
path,
|
||||||
|
"\" id=\"file\">",
|
||||||
|
"<article>",
|
||||||
|
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
||||||
|
"</article>",
|
||||||
|
"</div>"
|
||||||
|
).mkString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "application/vnd.github.v3.html" => {
|
||||||
|
contentType = "application/vnd.github.v3.html"
|
||||||
|
content.map { c =>
|
||||||
|
List(
|
||||||
|
"<div data-path=\"",
|
||||||
|
path,
|
||||||
|
"\" id=\"file\">",
|
||||||
|
"<div class=\"plain\">",
|
||||||
|
"<pre>",
|
||||||
|
play.twirl.api.HtmlFormat.escape(new String(c)).body,
|
||||||
|
"</pre>",
|
||||||
|
"</div>",
|
||||||
|
"</div>"
|
||||||
|
).mkString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.getOrElse(NotFound())
|
||||||
|
|
||||||
|
} else { // directory
|
||||||
|
JsonFormat(fileList.map { f =>
|
||||||
|
ApiContents(f, RepositoryName(repository), None)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* iii. Create a file
|
||||||
|
* https://developer.github.com/v3/repos/contents/#create-a-file
|
||||||
|
* requested #2112
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update a file
|
||||||
|
* https://developer.github.com/v3/repos/contents/#update-a-file
|
||||||
|
* requested #2112
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Delete a file
|
||||||
|
* https://developer.github.com/v3/repos/contents/#delete-a-file
|
||||||
|
* should be implemented
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Get archive link
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-archive-link
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryCreationService, RepositoryService}
|
||||||
|
import gitbucket.core.servlet.Database
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
|
import scala.concurrent.Await
|
||||||
|
import scala.concurrent.duration.Duration
|
||||||
|
|
||||||
|
trait ApiRepositoryControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService
|
||||||
|
with RepositoryCreationService
|
||||||
|
with AccountService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* i. List your repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-your-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/user/repos")(usersOnly {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
||||||
|
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. List user repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-user-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
||||||
|
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iii. List organization repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-organization-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:orgName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
||||||
|
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. List all public repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-all-public-repositories
|
||||||
|
* Not implemented
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Create
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
* Implemented with two methods (user/orgs)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create user repository
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
*/
|
||||||
|
post("/api/v3/user/repos")(usersOnly {
|
||||||
|
val owner = context.loginAccount.get.userName
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(s"${owner}/${data.name}") {
|
||||||
|
if (getRepository(owner, data.name).isEmpty) {
|
||||||
|
val f = createRepository(
|
||||||
|
context.loginAccount.get,
|
||||||
|
owner,
|
||||||
|
data.name,
|
||||||
|
data.description,
|
||||||
|
data.`private`,
|
||||||
|
data.auto_init
|
||||||
|
)
|
||||||
|
Await.result(f, Duration.Inf)
|
||||||
|
|
||||||
|
val repository = Database() withTransaction { session =>
|
||||||
|
getRepository(owner, data.name)(session).get
|
||||||
|
}
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
||||||
|
} else {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists on this account",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create group repository
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
*/
|
||||||
|
post("/api/v3/orgs/:org/repos")(managersOnly {
|
||||||
|
val groupName = params("org")
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(s"${groupName}/${data.name}") {
|
||||||
|
if (getRepository(groupName, data.name).isEmpty) {
|
||||||
|
val f = createRepository(
|
||||||
|
context.loginAccount.get,
|
||||||
|
groupName,
|
||||||
|
data.name,
|
||||||
|
data.description,
|
||||||
|
data.`private`,
|
||||||
|
data.auto_init
|
||||||
|
)
|
||||||
|
Await.result(f, Duration.Inf)
|
||||||
|
val repository = Database() withTransaction { session =>
|
||||||
|
getRepository(groupName, data.name).get
|
||||||
|
}
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
||||||
|
} else {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists for this group",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Get
|
||||||
|
* https://developer.github.com/v3/repos/#get
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Edit
|
||||||
|
* https://developer.github.com/v3/repos/#edit
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. List all topics for a repository
|
||||||
|
* https://developer.github.com/v3/repos/#list-all-topics-for-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. Replace all topics for a repository
|
||||||
|
* https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. List contributors
|
||||||
|
* https://developer.github.com/v3/repos/#list-contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xi. List languages
|
||||||
|
* https://developer.github.com/v3/repos/#list-languages
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xii. List teams
|
||||||
|
* https://developer.github.com/v3/repos/#list-teams
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiii. List tags
|
||||||
|
* https://developer.github.com/v3/repos/#list-tags
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiv. Delete a repository
|
||||||
|
* https://developer.github.com/v3/repos/#delete-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xv. Transfer a repository
|
||||||
|
* https://developer.github.com/v3/repos/#transfer-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* non-GitHub compatible API for Jenkins-Plugin
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||||
|
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||||
|
|
||||||
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
|
responseRawFile(git, objectId, path, repository)
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.CommitState
|
||||||
|
import gitbucket.core.service.{AccountService, CommitStatusService}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||||
|
|
||||||
|
trait ApiRepositoryStatusControllerBase extends ControllerBase {
|
||||||
|
self: AccountService with CommitStatusService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Create a status
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/statuses/:sha")(writableUsersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
ref <- params.get("sha")
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
data <- extractFromJsonBody[CreateAStatus] if data.isValid
|
||||||
|
creator <- context.loginAccount
|
||||||
|
state <- CommitState.valueOf(data.state)
|
||||||
|
statusId = createCommitStatus(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
sha,
|
||||||
|
data.context.getOrElse("default"),
|
||||||
|
state,
|
||||||
|
data.target_url,
|
||||||
|
data.description,
|
||||||
|
new java.util.Date(),
|
||||||
|
creator
|
||||||
|
)
|
||||||
|
status <- getCommitStatus(repository.owner, repository.name, statusId)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List statuses for a specific ref
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||||
|
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||||
|
*/
|
||||||
|
val listStatusesRoute = get("/api/v3/repos/:owner/:repository/commits/:ref/statuses")(referrersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
ref <- params.get("ref")
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map {
|
||||||
|
case (status, creator) =>
|
||||||
|
ApiCommitStatus(status, ApiUser(creator))
|
||||||
|
})
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||||
|
* legacy route
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/statuses/:ref") {
|
||||||
|
listStatusesRoute.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get the combined status for a specific ref
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||||
|
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/commits/:ref/status")(referrersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
ref <- params.get("ref")
|
||||||
|
owner <- getAccountByUserName(repository.owner)
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
||||||
|
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiUser, CreateAUser, JsonFormat, UpdateAUser}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.scalatra.NoContent
|
||||||
|
|
||||||
|
trait ApiUserControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* i. Get a single user
|
||||||
|
* https://developer.github.com/v3/users/#get-a-single-user
|
||||||
|
* This API also returns group information (as GitHub).
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName") {
|
||||||
|
getAccountByUserName(params("userName")).map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get the authenticated user
|
||||||
|
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
||||||
|
*/
|
||||||
|
get("/api/v3/user") {
|
||||||
|
context.loginAccount.map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse Unauthorized()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Update the authenticated user
|
||||||
|
* https://developer.github.com/v3/users/#update-the-authenticated-user
|
||||||
|
*/
|
||||||
|
patch("/api/v3/user")(usersOnly {
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[UpdateAUser]
|
||||||
|
} yield {
|
||||||
|
val loginAccount = context.loginAccount.get
|
||||||
|
val updatedAccount = loginAccount.copy(
|
||||||
|
mailAddress = data.email.getOrElse(loginAccount.mailAddress)
|
||||||
|
)
|
||||||
|
updateAccount(updatedAccount)
|
||||||
|
JsonFormat(ApiUser(updatedAccount))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Get contextual information about a user
|
||||||
|
* https://developer.github.com/v3/users/#get-contextual-information-about-a-user
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Get all users
|
||||||
|
* https://developer.github.com/v3/users/#get-all-users
|
||||||
|
*/
|
||||||
|
get("/api/v3/users") {
|
||||||
|
JsonFormat(getAllUsers(false, false).map(a => ApiUser(a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ghe: i. Create a new user
|
||||||
|
* https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#create-a-new-user
|
||||||
|
*/
|
||||||
|
post("/api/v3/admin/users")(adminOnly {
|
||||||
|
for {
|
||||||
|
data <- extractFromJsonBody[CreateAUser]
|
||||||
|
} yield {
|
||||||
|
val user = createAccount(
|
||||||
|
data.login,
|
||||||
|
data.password,
|
||||||
|
data.fullName.getOrElse(data.login),
|
||||||
|
data.email,
|
||||||
|
data.isAdmin.getOrElse(false),
|
||||||
|
data.description,
|
||||||
|
data.url
|
||||||
|
)
|
||||||
|
JsonFormat(ApiUser(user))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ghe: vii. Suspend a user
|
||||||
|
* https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#suspend-a-user
|
||||||
|
*/
|
||||||
|
put("/api/v3/users/:userName/suspended")(adminOnly {
|
||||||
|
val userName = params("userName")
|
||||||
|
getAccountByUserName(userName) match {
|
||||||
|
case Some(targetAccount) =>
|
||||||
|
removeUserRelatedData(userName)
|
||||||
|
updateAccount(targetAccount.copy(isRemoved = true))
|
||||||
|
NoContent()
|
||||||
|
case None =>
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ghe: vii. Unsuspend a user
|
||||||
|
* https://developer.github.com/enterprise/2.14/v3/enterprise-admin/users/#unsuspend-a-user
|
||||||
|
*/
|
||||||
|
delete("/api/v3/users/:userName/suspended")(adminOnly {
|
||||||
|
val userName = params("userName")
|
||||||
|
getAccountByUserName(userName) match {
|
||||||
|
case Some(targetAccount) =>
|
||||||
|
updateAccount(targetAccount.copy(isRemoved = false))
|
||||||
|
NoContent()
|
||||||
|
case None =>
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
|
|||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import io.github.gitbucket.solidbase.model.Version
|
import io.github.gitbucket.solidbase.model.Version
|
||||||
import org.apache.sshd.server.Command
|
import org.apache.sshd.server.command.Command
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +47,20 @@ abstract class Plugin {
|
|||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
): Seq[(String, ControllerBase)] = Nil
|
): Seq[(String, ControllerBase)] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to declare this plug-in provides anonymous accessible paths.
|
||||||
|
*/
|
||||||
|
val anonymousAccessiblePaths: Seq[String] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to declare this plug-in provides anonymous accessible paths.
|
||||||
|
*/
|
||||||
|
def anonymousAccessiblePaths(
|
||||||
|
registry: PluginRegistry,
|
||||||
|
context: ServletContext,
|
||||||
|
settings: SystemSettings
|
||||||
|
): Seq[String] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override to declare this plug-in provides JavaScript.
|
* Override to declare this plug-in provides JavaScript.
|
||||||
*/
|
*/
|
||||||
@@ -333,6 +347,10 @@ abstract class Plugin {
|
|||||||
case (path, controller) =>
|
case (path, controller) =>
|
||||||
registry.addController(path, controller)
|
registry.addController(path, controller)
|
||||||
}
|
}
|
||||||
|
(anonymousAccessiblePaths ++ anonymousAccessiblePaths(registry, context, settings)).foreach {
|
||||||
|
case (path) =>
|
||||||
|
registry.addAnonymousAccessiblePath(path)
|
||||||
|
}
|
||||||
(javaScripts ++ javaScripts(registry, context, settings)).foreach {
|
(javaScripts ++ javaScripts(registry, context, settings)).foreach {
|
||||||
case (path, script) =>
|
case (path, script) =>
|
||||||
registry.addJavaScript(path, script)
|
registry.addJavaScript(path, script)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package gitbucket.core.plugin
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
import java.io.{File, FilenameFilter, InputStream}
|
import java.io.{File, FilenameFilter}
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.nio.file.{Files, Paths, StandardWatchEventKinds}
|
import java.nio.file.{Files, Paths, StandardWatchEventKinds}
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.servlet.ServletContext
|
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext
|
||||||
import com.github.zafarkhaja.semver.Version
|
import com.github.zafarkhaja.semver.Version
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
import gitbucket.core.model.{Account, Issue}
|
import gitbucket.core.model.{Account, Issue}
|
||||||
@@ -15,14 +15,15 @@ import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
|||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.DatabaseConfig
|
import gitbucket.core.util.DatabaseConfig
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.HttpClientUtil._
|
||||||
import io.github.gitbucket.solidbase.Solidbase
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
import io.github.gitbucket.solidbase.model.Module
|
import io.github.gitbucket.solidbase.model.Module
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.apache.sshd.server.Command
|
import org.apache.http.client.methods.HttpGet
|
||||||
|
import org.apache.sshd.server.command.Command
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ class PluginRegistry {
|
|||||||
private val plugins = new ConcurrentLinkedQueue[PluginInfo]
|
private val plugins = new ConcurrentLinkedQueue[PluginInfo]
|
||||||
private val javaScripts = new ConcurrentLinkedQueue[(String, String)]
|
private val javaScripts = new ConcurrentLinkedQueue[(String, String)]
|
||||||
private val controllers = new ConcurrentLinkedQueue[(ControllerBase, String)]
|
private val controllers = new ConcurrentLinkedQueue[(ControllerBase, String)]
|
||||||
|
private val anonymousAccessiblePaths = new ConcurrentLinkedQueue[String]
|
||||||
private val images = new ConcurrentHashMap[String, String]
|
private val images = new ConcurrentHashMap[String, String]
|
||||||
private val renderers = new ConcurrentHashMap[String, Renderer]
|
private val renderers = new ConcurrentHashMap[String, Renderer]
|
||||||
renderers.put("md", MarkdownRenderer)
|
renderers.put("md", MarkdownRenderer)
|
||||||
@@ -68,25 +70,16 @@ class PluginRegistry {
|
|||||||
images.put(id, encoded)
|
images.put(id, encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0")
|
|
||||||
def addImage(id: String, in: InputStream): Unit = {
|
|
||||||
val bytes = using(in) { in =>
|
|
||||||
val bytes = new Array[Byte](in.available)
|
|
||||||
in.read(bytes)
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
addImage(id, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
def getImage(id: String): String = images.get(id)
|
def getImage(id: String): String = images.get(id)
|
||||||
|
|
||||||
def addController(path: String, controller: ControllerBase): Unit = controllers.add((controller, path))
|
def addController(path: String, controller: ControllerBase): Unit = controllers.add((controller, path))
|
||||||
|
|
||||||
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
|
|
||||||
def addController(controller: ControllerBase, path: String): Unit = addController(path, controller)
|
|
||||||
|
|
||||||
def getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq
|
def getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq
|
||||||
|
|
||||||
|
def addAnonymousAccessiblePath(path: String): Unit = anonymousAccessiblePaths.add(path)
|
||||||
|
|
||||||
|
def getAnonymousAccessiblePaths(): Seq[String] = anonymousAccessiblePaths.asScala.toSeq
|
||||||
|
|
||||||
def addJavaScript(path: String, script: String): Unit =
|
def addJavaScript(path: String, script: String): Unit =
|
||||||
javaScripts.add((path, script)) //javaScripts += ((path, script))
|
javaScripts.add((path, script)) //javaScripts += ((path, script))
|
||||||
|
|
||||||
@@ -253,8 +246,17 @@ object PluginRegistry {
|
|||||||
})
|
})
|
||||||
.foreach(_.delete())
|
.foreach(_.delete())
|
||||||
|
|
||||||
val in = url.openStream()
|
withHttpClient(settings.pluginProxy) { httpClient =>
|
||||||
FileUtils.copyToFile(in, new File(PluginHome, new File(url.getFile).getName))
|
val httpGet = new HttpGet(url.toString)
|
||||||
|
try {
|
||||||
|
val response = httpClient.execute(httpGet)
|
||||||
|
val in = response.getEntity.getContent
|
||||||
|
FileUtils.copyToFile(in, new File(PluginHome, new File(url.getFile).getName))
|
||||||
|
} finally {
|
||||||
|
httpGet.releaseConnection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
instance = new PluginRegistry()
|
instance = new PluginRegistry()
|
||||||
initialize(context, settings, conn)
|
initialize(context, settings, conn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package gitbucket.core.plugin
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import gitbucket.core.controller.Context
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.util.HttpClientUtil._
|
||||||
import org.json4s._
|
import org.json4s._
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.HttpGet
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
object PluginRepository {
|
object PluginRepository {
|
||||||
@@ -12,18 +17,28 @@ object PluginRepository {
|
|||||||
org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]]
|
org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]]
|
||||||
}
|
}
|
||||||
|
|
||||||
def getPlugins(): Seq[PluginMetadata] = {
|
def getPlugins()(implicit context: Context): Seq[PluginMetadata] = {
|
||||||
try {
|
try {
|
||||||
val url = new java.net.URL("https://plugins.gitbucket-community.org/releases/plugins.json")
|
val url = new java.net.URL("https://plugins.gitbucket-community.org/releases/plugins.json")
|
||||||
val str = IOUtils.toString(url, "UTF-8")
|
|
||||||
parsePluginJson(str)
|
withHttpClient(context.settings.pluginProxy) { httpClient =>
|
||||||
|
val httpGet = new HttpGet(url.toString)
|
||||||
|
try {
|
||||||
|
val response = httpClient.execute(httpGet)
|
||||||
|
using(response.getEntity.getContent) { in =>
|
||||||
|
val str = IOUtils.toString(in, "UTF-8")
|
||||||
|
parsePluginJson(str)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
httpGet.releaseConnection()
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case t: Throwable =>
|
case t: Throwable =>
|
||||||
logger.warn("Failed to access to the plugin repository: " + t.toString)
|
logger.warn("Failed to access to the plugin repository: " + t.toString)
|
||||||
Nil
|
Nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapped from plugins.json
|
// Mapped from plugins.json
|
||||||
|
|||||||
@@ -25,10 +25,13 @@ object MarkdownRenderer extends Renderer {
|
|||||||
Markdown.toHtml(
|
Markdown.toHtml(
|
||||||
markdown = fileContent,
|
markdown = fileContent,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = branch,
|
||||||
enableWikiLink = enableWikiLink,
|
enableWikiLink = enableWikiLink,
|
||||||
enableRefsLink = enableRefsLink,
|
enableRefsLink = enableRefsLink,
|
||||||
enableAnchor = enableAnchor,
|
enableAnchor = enableAnchor,
|
||||||
enableLineBreaks = false
|
enableLineBreaks = false,
|
||||||
|
enableTaskList = true,
|
||||||
|
hasWritePermission = false
|
||||||
)(context)
|
)(context)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
|||||||
import gitbucket.core.model.Profile.dateColumnType
|
import gitbucket.core.model.Profile.dateColumnType
|
||||||
import gitbucket.core.util.{LDAPUtil, StringUtil}
|
import gitbucket.core.util.{LDAPUtil, StringUtil}
|
||||||
import StringUtil._
|
import StringUtil._
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
|
|
||||||
trait AccountService {
|
trait AccountService {
|
||||||
@@ -163,8 +164,8 @@ trait AccountService {
|
|||||||
isAdmin: Boolean,
|
isAdmin: Boolean,
|
||||||
description: Option[String],
|
description: Option[String],
|
||||||
url: Option[String]
|
url: Option[String]
|
||||||
)(implicit s: Session): Unit =
|
)(implicit s: Session): Account = {
|
||||||
Accounts insert Account(
|
val account = Account(
|
||||||
userName = userName,
|
userName = userName,
|
||||||
password = password,
|
password = password,
|
||||||
fullName = fullName,
|
fullName = fullName,
|
||||||
@@ -179,6 +180,18 @@ trait AccountService {
|
|||||||
isRemoved = false,
|
isRemoved = false,
|
||||||
description = description
|
description = description
|
||||||
)
|
)
|
||||||
|
Accounts insert account
|
||||||
|
account
|
||||||
|
}
|
||||||
|
|
||||||
|
def suspendAccount(account: Account)(implicit s: Session): Unit = {
|
||||||
|
// Remove from GROUP_MEMBER and COLLABORATOR
|
||||||
|
removeUserRelatedData(account.userName)
|
||||||
|
updateAccount(account.copy(isRemoved = true))
|
||||||
|
|
||||||
|
// call hooks
|
||||||
|
PluginRegistry().getAccountHooks.foreach(_.deleted(account.userName))
|
||||||
|
}
|
||||||
|
|
||||||
def updateAccount(account: Account)(implicit s: Session): Unit =
|
def updateAccount(account: Account)(implicit s: Session): Unit =
|
||||||
Accounts
|
Accounts
|
||||||
@@ -278,6 +291,15 @@ trait AccountService {
|
|||||||
Collaborators.filter(_.collaboratorName === userName.bind).delete
|
Collaborators.filter(_.collaboratorName === userName.bind).delete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def removeUser(account: Account)(implicit s: Session): Unit = {
|
||||||
|
// Remove from GROUP_MEMBER and COLLABORATOR
|
||||||
|
removeUserRelatedData(account.userName)
|
||||||
|
updateAccount(account.copy(isRemoved = true))
|
||||||
|
|
||||||
|
// call hooks
|
||||||
|
PluginRegistry().getAccountHooks.foreach(_.deleted(account.userName))
|
||||||
|
}
|
||||||
|
|
||||||
def getGroupNames(userName: String)(implicit s: Session): List[String] = {
|
def getGroupNames(userName: String)(implicit s: Session): List[String] = {
|
||||||
List(userName) ++
|
List(userName) ++
|
||||||
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct
|
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ trait ActivityService {
|
|||||||
repositoryName,
|
repositoryName,
|
||||||
activityUserName,
|
activityUserName,
|
||||||
"release",
|
"release",
|
||||||
s"[user:${activityUserName}] released ${name} at [repo:${userName}/${repositoryName}]",
|
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${name}] at [repo:${userName}/${repositoryName}]",
|
||||||
None,
|
None,
|
||||||
currentDate
|
currentDate
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ package gitbucket.core.service
|
|||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
import gitbucket.core.model.CommitComment
|
import gitbucket.core.api.JsonFormat
|
||||||
|
import gitbucket.core.controller.Context
|
||||||
|
import gitbucket.core.model.{Account, CommitComment}
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import gitbucket.core.model.Profile.dateColumnType
|
import gitbucket.core.model.Profile.dateColumnType
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.{FileUtil, StringUtil}
|
import gitbucket.core.util.{FileUtil, StringUtil}
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
trait CommitsService {
|
trait CommitsService {
|
||||||
|
self: ActivityService with PullRequestService with WebHookPullRequestReviewCommentService =>
|
||||||
|
|
||||||
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(
|
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(
|
||||||
implicit s: Session
|
implicit s: Session
|
||||||
@@ -28,21 +33,21 @@ trait CommitsService {
|
|||||||
None
|
None
|
||||||
|
|
||||||
def createCommitComment(
|
def createCommitComment(
|
||||||
owner: String,
|
repository: RepositoryInfo,
|
||||||
repository: String,
|
|
||||||
commitId: String,
|
commitId: String,
|
||||||
loginUser: String,
|
loginAccount: Account,
|
||||||
content: String,
|
content: String,
|
||||||
fileName: Option[String],
|
fileName: Option[String],
|
||||||
oldLine: Option[Int],
|
oldLine: Option[Int],
|
||||||
newLine: Option[Int],
|
newLine: Option[Int],
|
||||||
|
diff: Option[String],
|
||||||
issueId: Option[Int]
|
issueId: Option[Int]
|
||||||
)(implicit s: Session): Int =
|
)(implicit s: Session, c: JsonFormat.Context, context: Context): Int = {
|
||||||
CommitComments returning CommitComments.map(_.commentId) insert CommitComment(
|
val commentId = CommitComments returning CommitComments.map(_.commentId) insert CommitComment(
|
||||||
userName = owner,
|
userName = repository.owner,
|
||||||
repositoryName = repository,
|
repositoryName = repository.name,
|
||||||
commitId = commitId,
|
commitId = commitId,
|
||||||
commentedUserName = loginUser,
|
commentedUserName = loginAccount.userName,
|
||||||
content = content,
|
content = content,
|
||||||
fileName = fileName,
|
fileName = fileName,
|
||||||
oldLine = oldLine,
|
oldLine = oldLine,
|
||||||
@@ -55,6 +60,56 @@ trait CommitsService {
|
|||||||
originalNewLine = newLine
|
originalNewLine = newLine
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
fileName <- fileName
|
||||||
|
diff <- diff
|
||||||
|
} {
|
||||||
|
saveCommitCommentDiff(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
commitId,
|
||||||
|
fileName,
|
||||||
|
oldLine,
|
||||||
|
newLine,
|
||||||
|
diff
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
|
||||||
|
issueId match {
|
||||||
|
case Some(issueId) =>
|
||||||
|
getPullRequest(repository.owner, repository.name, issueId).foreach {
|
||||||
|
case (issue, pullRequest) =>
|
||||||
|
recordCommentPullRequestActivity(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
loginAccount.userName,
|
||||||
|
issueId,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, content, issue, repository))
|
||||||
|
callPullRequestReviewCommentWebHook(
|
||||||
|
"create",
|
||||||
|
comment,
|
||||||
|
repository,
|
||||||
|
issue,
|
||||||
|
pullRequest,
|
||||||
|
loginAccount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
recordCommentCommitActivity(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
loginAccount.userName,
|
||||||
|
commitId,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
commentId
|
||||||
|
}
|
||||||
|
|
||||||
def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])(
|
def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])(
|
||||||
implicit s: Session
|
implicit s: Session
|
||||||
): Unit =
|
): Unit =
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ trait HandleCommentService {
|
|||||||
case "reopen" => "reopened"
|
case "reopen" => "reopened"
|
||||||
}
|
}
|
||||||
if (issue.isPullRequest)
|
if (issue.isPullRequest)
|
||||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, loginAccount)
|
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount)
|
||||||
else
|
else
|
||||||
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, loginAccount)
|
callIssuesWebHook(webHookAction, repository, issue, loginAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// call hooks
|
// call hooks
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ trait IssueCreationService {
|
|||||||
createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount)
|
createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount)
|
||||||
|
|
||||||
// call web hooks
|
// call web hooks
|
||||||
callIssuesWebHook("opened", repository, issue, context.baseUrl, loginAccount)
|
callIssuesWebHook("opened", repository, issue, loginAccount)
|
||||||
|
|
||||||
// call hooks
|
// call hooks
|
||||||
PluginRegistry().getIssueHooks.foreach(_.created(issue, repository))
|
PluginRegistry().getIssueHooks.foreach(_.created(issue, repository))
|
||||||
|
|||||||
@@ -351,9 +351,11 @@ trait IssuesService {
|
|||||||
implicit s: Session
|
implicit s: Session
|
||||||
) =
|
) =
|
||||||
Issues filter { t1 =>
|
Issues filter { t1 =>
|
||||||
repos
|
(if (repos.size == 1) {
|
||||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
t1.byRepository(repos.head._1, repos.head._2)
|
||||||
.foldLeft[Rep[Boolean]](false)(_ || _) &&
|
} else {
|
||||||
|
((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" }))
|
||||||
|
}) &&
|
||||||
(t1.closed === (condition.state == "closed").bind) &&
|
(t1.closed === (condition.state == "closed").bind) &&
|
||||||
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
|
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
|
||||||
(t1.priorityId.? isEmpty, condition.priority == Some(None)) &&
|
(t1.priorityId.? isEmpty, condition.priority == Some(None)) &&
|
||||||
@@ -473,6 +475,25 @@ trait IssuesService {
|
|||||||
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) delete
|
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) delete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def deleteAllIssueLabels(owner: String, repository: String, issueId: Int, insertComment: Boolean = false)(
|
||||||
|
implicit context: Context,
|
||||||
|
s: Session
|
||||||
|
): Int = {
|
||||||
|
if (insertComment) {
|
||||||
|
IssueComments insert IssueComment(
|
||||||
|
userName = owner,
|
||||||
|
repositoryName = repository,
|
||||||
|
issueId = issueId,
|
||||||
|
action = "delete_label",
|
||||||
|
commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"),
|
||||||
|
content = "All labels",
|
||||||
|
registeredDate = currentDate,
|
||||||
|
updatedDate = currentDate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IssueLabels filter (_.byIssue(owner, repository, issueId)) delete
|
||||||
|
}
|
||||||
|
|
||||||
def createComment(
|
def createComment(
|
||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
@@ -505,6 +526,15 @@ trait IssuesService {
|
|||||||
.update(title, content, currentDate)
|
.update(title, content, currentDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session) = {
|
||||||
|
Issues
|
||||||
|
.filter(_.byPrimaryKey(owner, repository, issueId))
|
||||||
|
.map { t =>
|
||||||
|
t.pullRequest
|
||||||
|
}
|
||||||
|
.update(true)
|
||||||
|
}
|
||||||
|
|
||||||
def updateAssignedUserName(
|
def updateAssignedUserName(
|
||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package gitbucket.core.service
|
|||||||
import gitbucket.core.model.Label
|
import gitbucket.core.model.Label
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
|
import gitbucket.core.util.StringUtil
|
||||||
|
|
||||||
trait LabelsService {
|
trait LabelsService {
|
||||||
|
|
||||||
@@ -24,6 +25,11 @@ trait LabelsService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def createLabel(owner: String, repository: String, labelName: String)(implicit s: Session): Int = {
|
||||||
|
val color = StringUtil.md5(labelName).substring(0, 6)
|
||||||
|
createLabel(owner, repository, labelName, color)
|
||||||
|
}
|
||||||
|
|
||||||
def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String)(
|
def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String)(
|
||||||
implicit s: Session
|
implicit s: Session
|
||||||
): Unit =
|
): Unit =
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.api.JsonFormat
|
||||||
|
import gitbucket.core.controller.Context
|
||||||
|
import gitbucket.core.model.{Account, PullRequest, WebHook}
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import gitbucket.core.model.Profile.profile._
|
||||||
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
|
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
import org.eclipse.jgit.transport.RefSpec
|
||||||
@@ -13,6 +21,13 @@ import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
|
|||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
trait MergeService {
|
trait MergeService {
|
||||||
|
self: AccountService
|
||||||
|
with ActivityService
|
||||||
|
with IssuesService
|
||||||
|
with RepositoryService
|
||||||
|
with PullRequestService
|
||||||
|
with WebHookPullRequestService =>
|
||||||
|
|
||||||
import MergeService._
|
import MergeService._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +58,13 @@ trait MergeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** merge the pull request with a merge commit */
|
/** merge the pull request with a merge commit */
|
||||||
def mergePullRequest(git: Git, branch: String, issueId: Int, message: String, committer: PersonIdent): Unit = {
|
def mergePullRequest(
|
||||||
|
git: Git,
|
||||||
|
branch: String,
|
||||||
|
issueId: Int,
|
||||||
|
message: String,
|
||||||
|
committer: PersonIdent
|
||||||
|
): ObjectId = {
|
||||||
new MergeCacheInfo(git, branch, issueId).merge(message, committer)
|
new MergeCacheInfo(git, branch, issueId).merge(message, committer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,12 +75,18 @@ trait MergeService {
|
|||||||
issueId: Int,
|
issueId: Int,
|
||||||
commits: Seq[RevCommit],
|
commits: Seq[RevCommit],
|
||||||
committer: PersonIdent
|
committer: PersonIdent
|
||||||
): Unit = {
|
): ObjectId = {
|
||||||
new MergeCacheInfo(git, branch, issueId).rebase(committer, commits)
|
new MergeCacheInfo(git, branch, issueId).rebase(committer, commits)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** squash commits in the pull request and append it */
|
/** squash commits in the pull request and append it */
|
||||||
def squashPullRequest(git: Git, branch: String, issueId: Int, message: String, committer: PersonIdent): Unit = {
|
def squashPullRequest(
|
||||||
|
git: Git,
|
||||||
|
branch: String,
|
||||||
|
issueId: Int,
|
||||||
|
message: String,
|
||||||
|
committer: PersonIdent
|
||||||
|
): ObjectId = {
|
||||||
new MergeCacheInfo(git, branch, issueId).squash(message, committer)
|
new MergeCacheInfo(git, branch, issueId).squash(message, committer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,27 +163,223 @@ trait MergeService {
|
|||||||
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption
|
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption
|
||||||
|
|
||||||
def pullRemote(
|
def pullRemote(
|
||||||
localUserName: String,
|
localRepository: RepositoryInfo,
|
||||||
localRepositoryName: String,
|
|
||||||
localBranch: String,
|
localBranch: String,
|
||||||
remoteUserName: String,
|
remoteRepository: RepositoryInfo,
|
||||||
remoteRepositoryName: String,
|
|
||||||
remoteBranch: String,
|
remoteBranch: String,
|
||||||
loginAccount: Account,
|
loginAccount: Account,
|
||||||
message: String
|
message: String,
|
||||||
): Option[ObjectId] = {
|
pullreq: Option[PullRequest]
|
||||||
|
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
|
||||||
|
val localUserName = localRepository.owner
|
||||||
|
val localRepositoryName = localRepository.name
|
||||||
|
val remoteUserName = remoteRepository.owner
|
||||||
|
val remoteRepositoryName = remoteRepository.name
|
||||||
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map {
|
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map {
|
||||||
case (newTreeId, oldBaseId, oldHeadId) =>
|
case (newTreeId, oldBaseId, oldHeadId) =>
|
||||||
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||||
|
val existIds = JGitUtil.getAllCommitIds(git).toSet
|
||||||
|
|
||||||
val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||||
val newCommit =
|
val newCommit =
|
||||||
Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId))
|
Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId))
|
||||||
Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge"))
|
Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge"))
|
||||||
|
|
||||||
|
val commits = git.log
|
||||||
|
.addRange(oldBaseId, newCommit)
|
||||||
|
.call
|
||||||
|
.iterator
|
||||||
|
.asScala
|
||||||
|
.map(c => new JGitUtil.CommitInfo(c))
|
||||||
|
.toList
|
||||||
|
|
||||||
|
commits.foreach { commit =>
|
||||||
|
if (!existIds.contains(commit.id)) {
|
||||||
|
createIssueComment(localUserName, localRepositoryName, commit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
recordPushActivity(
|
||||||
|
localUserName,
|
||||||
|
localRepositoryName,
|
||||||
|
loginAccount.userName,
|
||||||
|
localBranch,
|
||||||
|
commits
|
||||||
|
)
|
||||||
|
|
||||||
|
// close issue by commit message
|
||||||
|
if (localBranch == localRepository.repository.defaultBranch) {
|
||||||
|
commits.foreach { commit =>
|
||||||
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, localUserName, localRepositoryName)
|
||||||
|
.foreach { issueId =>
|
||||||
|
getIssue(localRepository.owner, localRepository.name, issueId.toString).foreach { issue =>
|
||||||
|
callIssuesWebHook("closed", localRepository, issue, loginAccount)
|
||||||
|
PluginRegistry().getIssueHooks
|
||||||
|
.foreach(
|
||||||
|
_.closedByCommitComment(issue, localRepository, commit.fullMessage, loginAccount)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pullreq.foreach { pullreq =>
|
||||||
|
callWebHookOf(localRepository.owner, localRepository.name, WebHook.Push) {
|
||||||
|
for {
|
||||||
|
ownerAccount <- getAccountByUserName(localRepository.owner)
|
||||||
|
} yield {
|
||||||
|
WebHookService.WebHookPushPayload(
|
||||||
|
git,
|
||||||
|
loginAccount,
|
||||||
|
pullreq.requestBranch,
|
||||||
|
localRepository,
|
||||||
|
commits,
|
||||||
|
ownerAccount,
|
||||||
|
oldId = oldBaseId,
|
||||||
|
newId = newCommit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
oldBaseId
|
oldBaseId
|
||||||
}.toOption
|
}.toOption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def mergePullRequest(
|
||||||
|
repository: RepositoryInfo,
|
||||||
|
issueId: Int,
|
||||||
|
loginAccount: Account,
|
||||||
|
message: String,
|
||||||
|
strategy: String
|
||||||
|
)(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, ObjectId] = {
|
||||||
|
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
|
||||||
|
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
|
getPullRequest(repository.owner, repository.name, issueId)
|
||||||
|
.map {
|
||||||
|
case (issue, pullreq) =>
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
// mark issue as merged and close.
|
||||||
|
val commentId =
|
||||||
|
createComment(repository.owner, repository.name, loginAccount.userName, issueId, message, "merge")
|
||||||
|
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close")
|
||||||
|
updateClosed(repository.owner, repository.name, issueId, true)
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, message)
|
||||||
|
|
||||||
|
val (commits, _) = getRequestCompareInfo(
|
||||||
|
repository.owner,
|
||||||
|
repository.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
|
||||||
|
(strategy match {
|
||||||
|
case "merge-commit" =>
|
||||||
|
Some(
|
||||||
|
mergePullRequest(
|
||||||
|
git,
|
||||||
|
pullreq.branch,
|
||||||
|
issueId,
|
||||||
|
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + message,
|
||||||
|
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case "rebase" =>
|
||||||
|
Some(
|
||||||
|
rebasePullRequest(
|
||||||
|
git,
|
||||||
|
pullreq.branch,
|
||||||
|
issueId,
|
||||||
|
revCommits,
|
||||||
|
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case "squash" =>
|
||||||
|
Some(
|
||||||
|
squashPullRequest(
|
||||||
|
git,
|
||||||
|
pullreq.branch,
|
||||||
|
issueId,
|
||||||
|
s"${issue.title} (#${issueId})\n\n" + message,
|
||||||
|
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}) match {
|
||||||
|
case Some(newCommitId) =>
|
||||||
|
// close issue by content of pull request
|
||||||
|
val defaultBranch = getRepository(repository.owner, repository.name).get.repository.defaultBranch
|
||||||
|
if (pullreq.branch == defaultBranch) {
|
||||||
|
commits.flatten.foreach { commit =>
|
||||||
|
closeIssuesFromMessage(
|
||||||
|
commit.fullMessage,
|
||||||
|
loginAccount.userName,
|
||||||
|
repository.owner,
|
||||||
|
repository.name
|
||||||
|
).foreach { issueId =>
|
||||||
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||||
|
PluginRegistry().getIssueHooks
|
||||||
|
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val issueContent = issue.title + " " + issue.content.getOrElse("")
|
||||||
|
closeIssuesFromMessage(
|
||||||
|
issueContent,
|
||||||
|
loginAccount.userName,
|
||||||
|
repository.owner,
|
||||||
|
repository.name
|
||||||
|
).foreach { issueId =>
|
||||||
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||||
|
PluginRegistry().getIssueHooks
|
||||||
|
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
||||||
|
.foreach { issueId =>
|
||||||
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||||
|
PluginRegistry().getIssueHooks
|
||||||
|
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePullRequests(repository.owner, repository.name, pullreq.branch, loginAccount, "closed")
|
||||||
|
|
||||||
|
// call hooks
|
||||||
|
PluginRegistry().getPullRequestHooks.foreach { h =>
|
||||||
|
h.addedComment(commentId, message, issue, repository)
|
||||||
|
h.merged(issue, repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
Right(newCommitId)
|
||||||
|
case None =>
|
||||||
|
Left("Unknown strategy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => Left("Unknown error")
|
||||||
|
}
|
||||||
|
.getOrElse(Left("Pull request not found"))
|
||||||
|
}
|
||||||
|
} else Left("Strategy not allowed")
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object MergeService {
|
object MergeService {
|
||||||
@@ -191,13 +414,15 @@ object MergeService {
|
|||||||
force: Boolean,
|
force: Boolean,
|
||||||
committer: PersonIdent,
|
committer: PersonIdent,
|
||||||
refLogMessage: Option[String] = None
|
refLogMessage: Option[String] = None
|
||||||
): Unit = {
|
): ObjectId = {
|
||||||
val refUpdate = repository.updateRef(ref)
|
val refUpdate = repository.updateRef(ref)
|
||||||
refUpdate.setNewObjectId(newObjectId)
|
refUpdate.setNewObjectId(newObjectId)
|
||||||
refUpdate.setForceUpdate(force)
|
refUpdate.setForceUpdate(force)
|
||||||
refUpdate.setRefLogIdent(committer)
|
refUpdate.setRefLogIdent(committer)
|
||||||
refLogMessage.foreach(refUpdate.setRefLogMessage(_, true))
|
refLogMessage.foreach(refUpdate.setRefLogMessage(_, true))
|
||||||
refUpdate.update()
|
refUpdate.update()
|
||||||
|
|
||||||
|
newObjectId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +490,7 @@ object MergeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update branch from cache
|
// update branch from cache
|
||||||
def merge(message: String, committer: PersonIdent) = {
|
def merge(message: String, committer: PersonIdent): ObjectId = {
|
||||||
if (checkConflict().isDefined) {
|
if (checkConflict().isDefined) {
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
throw new RuntimeException("This pull request can't merge automatically.")
|
||||||
}
|
}
|
||||||
@@ -278,7 +503,7 @@ object MergeService {
|
|||||||
Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
|
Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
|
||||||
}
|
}
|
||||||
|
|
||||||
def rebase(committer: PersonIdent, commits: Seq[RevCommit]): Unit = {
|
def rebase(committer: PersonIdent, commits: Seq[RevCommit]): ObjectId = {
|
||||||
if (checkConflict().isDefined) {
|
if (checkConflict().isDefined) {
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
throw new RuntimeException("This pull request can't merge automatically.")
|
||||||
}
|
}
|
||||||
@@ -310,7 +535,7 @@ object MergeService {
|
|||||||
Util.updateRefs(repository, s"refs/heads/${branch}", previousId, false, committer, Some("rebased"))
|
Util.updateRefs(repository, s"refs/heads/${branch}", previousId, false, committer, Some("rebased"))
|
||||||
}
|
}
|
||||||
|
|
||||||
def squash(message: String, committer: PersonIdent): Unit = {
|
def squash(message: String, committer: PersonIdent): ObjectId = {
|
||||||
if (checkConflict().isDefined) {
|
if (checkConflict().isDefined) {
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
throw new RuntimeException("This pull request can't merge automatically.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,23 @@ import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
|||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import difflib.{Delta, DiffUtils}
|
import difflib.{Delta, DiffUtils}
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
import gitbucket.core.api.JsonFormat
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.JGitUtil
|
import gitbucket.core.util.JGitUtil
|
||||||
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo}
|
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo}
|
||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
import gitbucket.core.view.helpers
|
import gitbucket.core.view.helpers
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
trait PullRequestService { self: IssuesService with CommitsService =>
|
trait PullRequestService {
|
||||||
|
self: IssuesService with CommitsService with WebHookService with WebHookPullRequestService with RepositoryService =>
|
||||||
import PullRequestService._
|
import PullRequestService._
|
||||||
|
|
||||||
def getPullRequest(owner: String, repository: String, issueId: Int)(
|
def getPullRequest(owner: String, repository: String, issueId: Int)(
|
||||||
@@ -164,7 +169,10 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
|||||||
/**
|
/**
|
||||||
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
|
* 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 =
|
def updatePullRequests(owner: String, repository: String, branch: String, loginAccount: Account, action: String)(
|
||||||
|
implicit s: Session,
|
||||||
|
c: JsonFormat.Context
|
||||||
|
): Unit = {
|
||||||
getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq =>
|
getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq =>
|
||||||
if (Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run) {
|
if (Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run) {
|
||||||
// Update the git repository
|
// Update the git repository
|
||||||
@@ -204,8 +212,17 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
|||||||
|
|
||||||
// Update commit id in the PULL_REQUEST table
|
// Update commit id in the PULL_REQUEST table
|
||||||
updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
|
updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
|
||||||
|
|
||||||
|
// call web hook
|
||||||
|
callPullRequestWebHookByRequestBranch(
|
||||||
|
action,
|
||||||
|
getRepository(owner, repository).get,
|
||||||
|
pullreq.requestBranch,
|
||||||
|
loginAccount
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def getPullRequestByRequestCommit(
|
def getPullRequestByRequestCommit(
|
||||||
userName: String,
|
userName: String,
|
||||||
@@ -253,8 +270,8 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
|||||||
.map { diff =>
|
.map { diff =>
|
||||||
(diff.oldContent, diff.newContent) match {
|
(diff.oldContent, diff.newContent) match {
|
||||||
case (Some(oldContent), Some(newContent)) => {
|
case (Some(oldContent), Some(newContent)) => {
|
||||||
val oldLines = oldContent.replace("\r\n", "\n").split("\n")
|
val oldLines = convertLineSeparator(oldContent, "LF").split("\n")
|
||||||
val newLines = newContent.replace("\r\n", "\n").split("\n")
|
val newLines = convertLineSeparator(newContent, "LF").split("\n")
|
||||||
file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava))
|
file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava))
|
||||||
}
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
@@ -384,6 +401,58 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
|||||||
updateClosed(owner, repository, pull.issueId, true)
|
updateClosed(owner, repository, pull.issueId, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses branch identifier and extracts owner and branch name as tuple.
|
||||||
|
*
|
||||||
|
* - "owner:branch" to ("owner", "branch")
|
||||||
|
* - "branch" to ("defaultOwner", "branch")
|
||||||
|
*/
|
||||||
|
def parseCompareIdentifier(value: String, defaultOwner: String): (String, String) =
|
||||||
|
if (value.contains(':')) {
|
||||||
|
val array = value.split(":")
|
||||||
|
(array(0), array(1))
|
||||||
|
} else {
|
||||||
|
(defaultOwner, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPullRequestCommitFromTo(
|
||||||
|
originRepository: RepositoryInfo,
|
||||||
|
forkedRepository: RepositoryInfo,
|
||||||
|
originId: String,
|
||||||
|
forkedId: String
|
||||||
|
): (Option[ObjectId], Option[ObjectId]) = {
|
||||||
|
using(
|
||||||
|
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||||
|
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||||
|
) {
|
||||||
|
case (oldGit, newGit) =>
|
||||||
|
if (originRepository.branchList.contains(originId)) {
|
||||||
|
val forkedId2 =
|
||||||
|
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
||||||
|
|
||||||
|
val originId2 = JGitUtil.getForkedCommitId(
|
||||||
|
oldGit,
|
||||||
|
newGit,
|
||||||
|
originRepository.owner,
|
||||||
|
originRepository.name,
|
||||||
|
originId,
|
||||||
|
forkedRepository.owner,
|
||||||
|
forkedRepository.name,
|
||||||
|
forkedId2
|
||||||
|
)
|
||||||
|
|
||||||
|
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val originId2 =
|
||||||
|
originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId)
|
||||||
|
val forkedId2 =
|
||||||
|
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
||||||
|
|
||||||
|
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PullRequestService {
|
object PullRequestService {
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
package gitbucket.core.service
|
||||||
|
import gitbucket.core.api.JsonFormat
|
||||||
|
import gitbucket.core.model.{Account, WebHook}
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||||
|
import org.eclipse.jgit.lib._
|
||||||
|
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||||
|
|
||||||
|
trait RepositoryCommitFileService {
|
||||||
|
self: AccountService with ActivityService with IssuesService with PullRequestService with WebHookPullRequestService =>
|
||||||
|
import RepositoryCommitFileService._
|
||||||
|
|
||||||
|
def commitFiles(
|
||||||
|
repository: RepositoryService.RepositoryInfo,
|
||||||
|
files: Seq[CommitFile],
|
||||||
|
branch: String,
|
||||||
|
path: String,
|
||||||
|
message: String,
|
||||||
|
loginAccount: Account
|
||||||
|
)(
|
||||||
|
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||||
|
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||||
|
// prepend path to the filename
|
||||||
|
_commitFile(repository, branch, message, loginAccount)(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
def commitFile(
|
||||||
|
repository: RepositoryService.RepositoryInfo,
|
||||||
|
branch: String,
|
||||||
|
path: String,
|
||||||
|
newFileName: Option[String],
|
||||||
|
oldFileName: Option[String],
|
||||||
|
content: String,
|
||||||
|
charset: String,
|
||||||
|
message: String,
|
||||||
|
commit: String,
|
||||||
|
loginAccount: Account
|
||||||
|
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||||
|
|
||||||
|
val newPath = newFileName.map { newFileName =>
|
||||||
|
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||||
|
}
|
||||||
|
val oldPath = oldFileName.map { oldFileName =>
|
||||||
|
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
_commitFile(repository, branch, message, loginAccount) {
|
||||||
|
case (git, headTip, builder, inserter) =>
|
||||||
|
if (headTip.getName == commit) {
|
||||||
|
val permission = JGitUtil
|
||||||
|
.processTree(git, headTip) { (path, tree) =>
|
||||||
|
// Add all entries except the editing file
|
||||||
|
if (!newPath.contains(path) && !oldPath.contains(path)) {
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||||
|
}
|
||||||
|
// Retrieve permission if file exists to keep it
|
||||||
|
oldPath.collect { case x if x == path => tree.getEntryFileMode.getBits }
|
||||||
|
}
|
||||||
|
.flatten
|
||||||
|
.headOption
|
||||||
|
|
||||||
|
newPath.foreach { newPath =>
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
|
||||||
|
FileMode.fromBits(bits)
|
||||||
|
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
|
||||||
|
}
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def _commitFile(
|
||||||
|
repository: RepositoryService.RepositoryInfo,
|
||||||
|
branch: String,
|
||||||
|
message: String,
|
||||||
|
loginAccount: Account
|
||||||
|
)(
|
||||||
|
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||||
|
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||||
|
|
||||||
|
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
val builder = DirCache.newInCore.builder()
|
||||||
|
val inserter = git.getRepository.newObjectInserter()
|
||||||
|
val headName = s"refs/heads/${branch}"
|
||||||
|
val headTip = git.getRepository.resolve(headName)
|
||||||
|
|
||||||
|
f(git, headTip, builder, inserter)
|
||||||
|
|
||||||
|
val commitId = JGitUtil.createNewCommit(
|
||||||
|
git,
|
||||||
|
inserter,
|
||||||
|
headTip,
|
||||||
|
builder.getDirCache.writeTree(inserter),
|
||||||
|
headName,
|
||||||
|
loginAccount.fullName,
|
||||||
|
loginAccount.mailAddress,
|
||||||
|
message
|
||||||
|
)
|
||||||
|
|
||||||
|
inserter.flush()
|
||||||
|
inserter.close()
|
||||||
|
|
||||||
|
val receivePack = new ReceivePack(git.getRepository)
|
||||||
|
val receiveCommand = new ReceiveCommand(headTip, commitId, headName)
|
||||||
|
|
||||||
|
// call post commit hook
|
||||||
|
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||||
|
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
||||||
|
}.headOption
|
||||||
|
|
||||||
|
error match {
|
||||||
|
case Some(error) =>
|
||||||
|
// commit is rejected
|
||||||
|
// TODO Notify commit failure to edited user
|
||||||
|
val refUpdate = git.getRepository.updateRef(headName)
|
||||||
|
refUpdate.setNewObjectId(headTip)
|
||||||
|
refUpdate.setForceUpdate(true)
|
||||||
|
refUpdate.update()
|
||||||
|
|
||||||
|
case None =>
|
||||||
|
// update refs
|
||||||
|
val refUpdate = git.getRepository.updateRef(headName)
|
||||||
|
refUpdate.setNewObjectId(commitId)
|
||||||
|
refUpdate.setForceUpdate(false)
|
||||||
|
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||||
|
refUpdate.update()
|
||||||
|
|
||||||
|
// update pull request
|
||||||
|
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize")
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||||
|
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
|
||||||
|
|
||||||
|
// create issue comment by commit message
|
||||||
|
createIssueComment(repository.owner, repository.name, commitInfo)
|
||||||
|
|
||||||
|
// close issue by commit message
|
||||||
|
if (branch == repository.repository.defaultBranch) {
|
||||||
|
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach {
|
||||||
|
issueId =>
|
||||||
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||||
|
PluginRegistry().getIssueHooks
|
||||||
|
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call post commit hook
|
||||||
|
PluginRegistry().getReceiveHooks.foreach { hook =>
|
||||||
|
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||||
|
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
|
||||||
|
getAccountByUserName(repository.owner).map { ownerAccount =>
|
||||||
|
WebHookPushPayload(
|
||||||
|
git,
|
||||||
|
loginAccount,
|
||||||
|
headName,
|
||||||
|
repository,
|
||||||
|
List(commit),
|
||||||
|
ownerAccount,
|
||||||
|
oldId = headTip,
|
||||||
|
newId = commitId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object RepositoryCommitFileService {
|
||||||
|
case class CommitFile(id: String, name: String)
|
||||||
|
}
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
|
import gitbucket.core.api.JsonFormat
|
||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.model.{Account, Collaborator, Repository, RepositoryOptions, Role, ReleaseTag}
|
import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import gitbucket.core.model.Profile.dateColumnType
|
import gitbucket.core.model.Profile.dateColumnType
|
||||||
import gitbucket.core.util.JGitUtil.FileInfo
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||||
|
import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, getTemporaryDir, getWikiRepositoryDir}
|
||||||
|
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo}
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||||
|
import org.eclipse.jgit.lib.{Repository => _, _}
|
||||||
|
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||||
|
|
||||||
trait RepositoryService { self: AccountService =>
|
trait RepositoryService {
|
||||||
|
self: AccountService =>
|
||||||
import RepositoryService._
|
import RepositoryService._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,181 +77,232 @@ trait RepositoryService { self: AccountService =>
|
|||||||
(Repositories filter { t =>
|
(Repositories filter { t =>
|
||||||
t.byRepository(oldUserName, oldRepositoryName)
|
t.byRepository(oldUserName, oldRepositoryName)
|
||||||
} firstOption).foreach { repository =>
|
} firstOption).foreach { repository =>
|
||||||
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
|
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
|
||||||
|
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
|
||||||
|
|
||||||
val webHooks = RepositoryWebHooks.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val webHooks = RepositoryWebHooks.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val webHookEvents = RepositoryWebHookEvents.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val webHookEvents = RepositoryWebHookEvents.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val milestones = Milestones.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val milestones = Milestones.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val issueId = IssueId.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val issueId = IssueId.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val issues = Issues.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val issues = Issues.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val pullRequests = PullRequests.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val pullRequests = PullRequests.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val labels = Labels.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val labels = Labels.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val priorities = Priorities.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val priorities = Priorities.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val issueLabels = IssueLabels.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val issueLabels = IssueLabels.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val protectedBranches = ProtectedBranches.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val protectedBranches = ProtectedBranches.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val protectedBranchContexts =
|
val protectedBranchContexts =
|
||||||
ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val deployKeys = DeployKeys.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val deployKeys = DeployKeys.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val releases = ReleaseTags.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val releases = ReleaseTags.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
val releaseAssets = ReleaseAssets.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
val releaseAssets = ReleaseAssets.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||||
|
|
||||||
Repositories
|
Repositories
|
||||||
.filter { t =>
|
.filter { t =>
|
||||||
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
|
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
|
||||||
}
|
|
||||||
.map { t =>
|
|
||||||
t.originUserName -> t.originRepositoryName
|
|
||||||
}
|
|
||||||
.update(newUserName, newRepositoryName)
|
|
||||||
|
|
||||||
Repositories
|
|
||||||
.filter { t =>
|
|
||||||
(t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind)
|
|
||||||
}
|
|
||||||
.map { t =>
|
|
||||||
t.parentUserName -> t.parentRepositoryName
|
|
||||||
}
|
|
||||||
.update(newUserName, newRepositoryName)
|
|
||||||
|
|
||||||
// Updates activity fk before deleting repository because activity is sorted by activityId
|
|
||||||
// and it can't be changed by deleting-and-inserting record.
|
|
||||||
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
|
|
||||||
Activities
|
|
||||||
.filter(_.activityId === activity.activityId.bind)
|
|
||||||
.map(x => (x.userName, x.repositoryName))
|
|
||||||
.update(newUserName, newRepositoryName)
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteRepository(oldUserName, oldRepositoryName)
|
|
||||||
|
|
||||||
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)): _*)
|
|
||||||
|
|
||||||
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
|
||||||
val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list
|
|
||||||
Issues.insertAll(issues.map { x =>
|
|
||||||
x.copy(
|
|
||||||
userName = newUserName,
|
|
||||||
repositoryName = newRepositoryName,
|
|
||||||
milestoneId = x.milestoneId.map { id =>
|
|
||||||
newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId
|
|
||||||
},
|
|
||||||
priorityId = x.priorityId.map { id =>
|
|
||||||
newPriorities.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName).get.priorityId
|
|
||||||
}
|
}
|
||||||
|
.map { t =>
|
||||||
|
t.originUserName -> t.originRepositoryName
|
||||||
|
}
|
||||||
|
.update(newUserName, newRepositoryName)
|
||||||
|
|
||||||
|
Repositories
|
||||||
|
.filter { t =>
|
||||||
|
(t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind)
|
||||||
|
}
|
||||||
|
.map { t =>
|
||||||
|
t.parentUserName -> t.parentRepositoryName
|
||||||
|
}
|
||||||
|
.update(newUserName, newRepositoryName)
|
||||||
|
|
||||||
|
// Updates activity fk before deleting repository because activity is sorted by activityId
|
||||||
|
// and it can't be changed by deleting-and-inserting record.
|
||||||
|
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
|
||||||
|
Activities
|
||||||
|
.filter(_.activityId === activity.activityId.bind)
|
||||||
|
.map(x => (x.userName, x.repositoryName))
|
||||||
|
.update(newUserName, newRepositoryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRepositoryOnModel(oldUserName, oldRepositoryName)
|
||||||
|
|
||||||
|
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)): _*)
|
||||||
|
|
||||||
PullRequests.insertAll(pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*)
|
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||||
IssueComments.insertAll(
|
val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||||
issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
Issues.insertAll(issues.map { x =>
|
||||||
)
|
x.copy(
|
||||||
Labels.insertAll(labels.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*)
|
userName = newUserName,
|
||||||
CommitComments.insertAll(
|
repositoryName = newRepositoryName,
|
||||||
commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
milestoneId = x.milestoneId.map { id =>
|
||||||
)
|
newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId
|
||||||
CommitStatuses.insertAll(
|
},
|
||||||
commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
priorityId = x.priorityId.map { id =>
|
||||||
)
|
newPriorities
|
||||||
ProtectedBranches.insertAll(
|
.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName)
|
||||||
protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
.get
|
||||||
)
|
.priorityId
|
||||||
ProtectedBranchContexts.insertAll(
|
}
|
||||||
protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
|
||||||
)
|
|
||||||
DeployKeys.insertAll(deployKeys.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*)
|
|
||||||
ReleaseTags.insertAll(releases.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*)
|
|
||||||
ReleaseAssets.insertAll(
|
|
||||||
releaseAssets.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update source repository of pull requests
|
|
||||||
PullRequests
|
|
||||||
.filter { t =>
|
|
||||||
(t.requestUserName === oldUserName.bind) && (t.requestRepositoryName === oldRepositoryName.bind)
|
|
||||||
}
|
|
||||||
.map { t =>
|
|
||||||
t.requestUserName -> t.requestRepositoryName
|
|
||||||
}
|
|
||||||
.update(newUserName, newRepositoryName)
|
|
||||||
|
|
||||||
// Convert labelId
|
|
||||||
val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap
|
|
||||||
val newLabelMap =
|
|
||||||
Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap
|
|
||||||
IssueLabels.insertAll(
|
|
||||||
issueLabels.map(
|
|
||||||
x =>
|
|
||||||
x.copy(
|
|
||||||
labelId = newLabelMap(oldLabelMap(x.labelId)),
|
|
||||||
userName = newUserName,
|
|
||||||
repositoryName = newRepositoryName
|
|
||||||
)
|
)
|
||||||
): _*
|
}: _*)
|
||||||
)
|
|
||||||
|
|
||||||
// TODO Drop transferred owner from collaborators?
|
PullRequests.insertAll(
|
||||||
Collaborators.insertAll(
|
pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
)
|
||||||
)
|
IssueComments.insertAll(
|
||||||
|
issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
|
)
|
||||||
|
Labels.insertAll(labels.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*)
|
||||||
|
CommitComments.insertAll(
|
||||||
|
commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
|
)
|
||||||
|
CommitStatuses.insertAll(
|
||||||
|
commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
|
)
|
||||||
|
ProtectedBranches.insertAll(
|
||||||
|
protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
|
)
|
||||||
|
ProtectedBranchContexts.insertAll(
|
||||||
|
protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
|
)
|
||||||
|
DeployKeys.insertAll(deployKeys.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*)
|
||||||
|
ReleaseTags.insertAll(releases.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*)
|
||||||
|
ReleaseAssets.insertAll(
|
||||||
|
releaseAssets.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
|
)
|
||||||
|
|
||||||
// Update activity messages
|
// Update source repository of pull requests
|
||||||
Activities
|
PullRequests
|
||||||
.filter { t =>
|
.filter { t =>
|
||||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
|
(t.requestUserName === oldUserName.bind) && (t.requestRepositoryName === oldRepositoryName.bind)
|
||||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%") ||
|
}
|
||||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}@%")
|
.map { t =>
|
||||||
|
t.requestUserName -> t.requestRepositoryName
|
||||||
|
}
|
||||||
|
.update(newUserName, newRepositoryName)
|
||||||
|
|
||||||
|
// Convert labelId
|
||||||
|
val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap
|
||||||
|
val newLabelMap =
|
||||||
|
Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap
|
||||||
|
IssueLabels.insertAll(
|
||||||
|
issueLabels.map(
|
||||||
|
x =>
|
||||||
|
x.copy(
|
||||||
|
labelId = newLabelMap(oldLabelMap(x.labelId)),
|
||||||
|
userName = newUserName,
|
||||||
|
repositoryName = newRepositoryName
|
||||||
|
)
|
||||||
|
): _*
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO Drop transferred owner from collaborators?
|
||||||
|
Collaborators.insertAll(
|
||||||
|
collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update activity messages
|
||||||
|
Activities
|
||||||
|
.filter { t =>
|
||||||
|
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
|
||||||
|
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%") ||
|
||||||
|
(t.message like s"%:${oldUserName}/${oldRepositoryName}@%")
|
||||||
|
}
|
||||||
|
.map { t =>
|
||||||
|
t.activityId -> t.message
|
||||||
|
}
|
||||||
|
.list
|
||||||
|
.foreach {
|
||||||
|
case (activityId, message) =>
|
||||||
|
Activities
|
||||||
|
.filter(_.activityId === activityId.bind)
|
||||||
|
.map(_.message)
|
||||||
|
.update(
|
||||||
|
message
|
||||||
|
.replace(
|
||||||
|
s"[repo:${oldUserName}/${oldRepositoryName}]",
|
||||||
|
s"[repo:${newUserName}/${newRepositoryName}]"
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
s"[branch:${oldUserName}/${oldRepositoryName}#",
|
||||||
|
s"[branch:${newUserName}/${newRepositoryName}#"
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
s"[tag:${oldUserName}/${oldRepositoryName}#",
|
||||||
|
s"[tag:${newUserName}/${newRepositoryName}#"
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
s"[pullreq:${oldUserName}/${oldRepositoryName}#",
|
||||||
|
s"[pullreq:${newUserName}/${newRepositoryName}#"
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
s"[issue:${oldUserName}/${oldRepositoryName}#",
|
||||||
|
s"[issue:${newUserName}/${newRepositoryName}#"
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
s"[commit:${oldUserName}/${oldRepositoryName}@",
|
||||||
|
s"[commit:${newUserName}/${newRepositoryName}@"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Move git repository
|
||||||
|
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
|
||||||
|
if (dir.isDirectory) {
|
||||||
|
FileUtils.moveDirectory(dir, getRepositoryDir(newUserName, newRepositoryName))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map { t =>
|
// Move wiki repository
|
||||||
t.activityId -> t.message
|
defining(getWikiRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
|
||||||
|
if (dir.isDirectory) {
|
||||||
|
FileUtils.moveDirectory(dir, getWikiRepositoryDir(newUserName, newRepositoryName))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.list
|
// Move files directory
|
||||||
.foreach {
|
defining(getRepositoryFilesDir(oldUserName, oldRepositoryName)) { dir =>
|
||||||
case (activityId, message) =>
|
if (dir.isDirectory) {
|
||||||
Activities
|
FileUtils.moveDirectory(dir, getRepositoryFilesDir(newUserName, newRepositoryName))
|
||||||
.filter(_.activityId === activityId.bind)
|
}
|
||||||
.map(_.message)
|
|
||||||
.update(
|
|
||||||
message
|
|
||||||
.replace(
|
|
||||||
s"[repo:${oldUserName}/${oldRepositoryName}]",
|
|
||||||
s"[repo:${newUserName}/${newRepositoryName}]"
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
s"[branch:${oldUserName}/${oldRepositoryName}#",
|
|
||||||
s"[branch:${newUserName}/${newRepositoryName}#"
|
|
||||||
)
|
|
||||||
.replace(s"[tag:${oldUserName}/${oldRepositoryName}#", s"[tag:${newUserName}/${newRepositoryName}#")
|
|
||||||
.replace(
|
|
||||||
s"[pullreq:${oldUserName}/${oldRepositoryName}#",
|
|
||||||
s"[pullreq:${newUserName}/${newRepositoryName}#"
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
s"[issue:${oldUserName}/${oldRepositoryName}#",
|
|
||||||
s"[issue:${newUserName}/${newRepositoryName}#"
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
s"[commit:${oldUserName}/${oldRepositoryName}@",
|
|
||||||
s"[commit:${newUserName}/${newRepositoryName}@"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
// Delete parent directory
|
||||||
|
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName))
|
||||||
|
|
||||||
|
// Call hooks
|
||||||
|
if (oldUserName == newUserName) {
|
||||||
|
PluginRegistry().getRepositoryHooks.foreach(_.renamed(oldUserName, oldRepositoryName, newRepositoryName))
|
||||||
|
} else {
|
||||||
|
PluginRegistry().getRepositoryHooks.foreach(_.transferred(oldUserName, newUserName, newRepositoryName))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
def deleteRepository(repository: Repository)(implicit s: Session): Unit = {
|
||||||
|
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
|
||||||
|
deleteRepositoryOnModel(repository.userName, repository.repositoryName)
|
||||||
|
|
||||||
|
FileUtils.deleteDirectory(getRepositoryDir(repository.userName, repository.repositoryName))
|
||||||
|
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.userName, repository.repositoryName))
|
||||||
|
FileUtils.deleteDirectory(getTemporaryDir(repository.userName, repository.repositoryName))
|
||||||
|
FileUtils.deleteDirectory(getRepositoryFilesDir(repository.userName, repository.repositoryName))
|
||||||
|
|
||||||
|
// Call hooks
|
||||||
|
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def deleteRepositoryOnModel(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
||||||
Activities.filter(_.byRepository(userName, repositoryName)).delete
|
Activities.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
Collaborators.filter(_.byRepository(userName, repositoryName)).delete
|
Collaborators.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
|
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
@@ -560,6 +620,14 @@ trait RepositoryService { self: AccountService =>
|
|||||||
): Unit =
|
): Unit =
|
||||||
Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role)
|
Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove specified collaborator from the repository.
|
||||||
|
*/
|
||||||
|
def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(
|
||||||
|
implicit s: Session
|
||||||
|
): Unit =
|
||||||
|
Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all collaborators from the repository.
|
* Remove all collaborators from the repository.
|
||||||
*/
|
*/
|
||||||
@@ -710,7 +778,6 @@ trait RepositoryService { self: AccountService =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
object RepositoryService {
|
object RepositoryService {
|
||||||
|
|
||||||
case class RepositoryInfo(
|
case class RepositoryInfo(
|
||||||
owner: String,
|
owner: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
|||||||
@@ -70,6 +70,16 @@ trait SystemSettingsService {
|
|||||||
props.setProperty(SkinName, settings.skinName.toString)
|
props.setProperty(SkinName, settings.skinName.toString)
|
||||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||||
props.setProperty(PluginNetworkInstall, settings.pluginNetworkInstall.toString)
|
props.setProperty(PluginNetworkInstall, settings.pluginNetworkInstall.toString)
|
||||||
|
settings.pluginProxy.foreach { proxy =>
|
||||||
|
props.setProperty(PluginProxyHost, proxy.host)
|
||||||
|
props.setProperty(PluginProxyPort, proxy.port.toString)
|
||||||
|
proxy.user.foreach { user =>
|
||||||
|
props.setProperty(PluginProxyUser, user)
|
||||||
|
}
|
||||||
|
proxy.password.foreach { password =>
|
||||||
|
props.setProperty(PluginProxyPassword, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
using(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||||
props.store(out, null)
|
props.store(out, null)
|
||||||
@@ -112,9 +122,7 @@ trait SystemSettingsService {
|
|||||||
getOptionValue(props, SmtpFromName, None)
|
getOptionValue(props, SmtpFromName, None)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else None,
|
||||||
None
|
|
||||||
},
|
|
||||||
getValue(props, LdapAuthentication, false),
|
getValue(props, LdapAuthentication, false),
|
||||||
if (getValue(props, LdapAuthentication, false)) {
|
if (getValue(props, LdapAuthentication, false)) {
|
||||||
Some(
|
Some(
|
||||||
@@ -133,9 +141,7 @@ trait SystemSettingsService {
|
|||||||
getOptionValue(props, LdapKeystore, None)
|
getOptionValue(props, LdapKeystore, None)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else None,
|
||||||
None
|
|
||||||
},
|
|
||||||
getValue(props, OidcAuthentication, false),
|
getValue(props, OidcAuthentication, false),
|
||||||
if (getValue(props, OidcAuthentication, false)) {
|
if (getValue(props, OidcAuthentication, false)) {
|
||||||
Some(
|
Some(
|
||||||
@@ -151,7 +157,17 @@ trait SystemSettingsService {
|
|||||||
},
|
},
|
||||||
getValue(props, SkinName, "skin-blue"),
|
getValue(props, SkinName, "skin-blue"),
|
||||||
getValue(props, ShowMailAddress, false),
|
getValue(props, ShowMailAddress, false),
|
||||||
getValue(props, PluginNetworkInstall, false)
|
getValue(props, PluginNetworkInstall, false),
|
||||||
|
if (getValue(props, PluginProxyHost, "").nonEmpty) {
|
||||||
|
Some(
|
||||||
|
Proxy(
|
||||||
|
getValue(props, PluginProxyHost, ""),
|
||||||
|
getValue(props, PluginProxyPort, 8080),
|
||||||
|
getOptionValue(props, PluginProxyUser, None),
|
||||||
|
getOptionValue(props, PluginProxyPassword, None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else None
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +197,8 @@ object SystemSettingsService {
|
|||||||
oidc: Option[OIDC],
|
oidc: Option[OIDC],
|
||||||
skinName: String,
|
skinName: String,
|
||||||
showMailAddress: Boolean,
|
showMailAddress: Boolean,
|
||||||
pluginNetworkInstall: Boolean
|
pluginNetworkInstall: Boolean,
|
||||||
|
pluginProxy: Option[Proxy]
|
||||||
) {
|
) {
|
||||||
|
|
||||||
def baseUrl(request: HttpServletRequest): String =
|
def baseUrl(request: HttpServletRequest): String =
|
||||||
@@ -249,6 +266,13 @@ object SystemSettingsService {
|
|||||||
fromName: Option[String]
|
fromName: Option[String]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case class Proxy(
|
||||||
|
host: String,
|
||||||
|
port: Int,
|
||||||
|
user: Option[String],
|
||||||
|
password: Option[String],
|
||||||
|
)
|
||||||
|
|
||||||
case class SshAddress(host: String, port: Int, genericUser: String)
|
case class SshAddress(host: String, port: Int, genericUser: String)
|
||||||
|
|
||||||
case class Lfs(serverUrl: Option[String])
|
case class Lfs(serverUrl: Option[String])
|
||||||
@@ -298,6 +322,10 @@ object SystemSettingsService {
|
|||||||
private val SkinName = "skinName"
|
private val SkinName = "skinName"
|
||||||
private val ShowMailAddress = "showMailAddress"
|
private val ShowMailAddress = "showMailAddress"
|
||||||
private val PluginNetworkInstall = "plugin.networkInstall"
|
private val PluginNetworkInstall = "plugin.networkInstall"
|
||||||
|
private val PluginProxyHost = "plugin.proxy.host"
|
||||||
|
private val PluginProxyPort = "plugin.proxy.port"
|
||||||
|
private val PluginProxyUser = "plugin.proxy.user"
|
||||||
|
private val PluginProxyPassword = "plugin.proxy.password"
|
||||||
|
|
||||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||||
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {
|
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {
|
||||||
|
|||||||
@@ -311,7 +311,6 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
action: String,
|
action: String,
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
issue: Issue,
|
issue: Issue,
|
||||||
baseUrl: String,
|
|
||||||
sender: Account
|
sender: Account
|
||||||
)(implicit s: Session, context: JsonFormat.Context): Unit = {
|
)(implicit s: Session, context: JsonFormat.Context): Unit = {
|
||||||
callWebHookOf(repository.owner, repository.name, WebHook.Issues) {
|
callWebHookOf(repository.owner, repository.name, WebHook.Issues) {
|
||||||
@@ -341,7 +340,6 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
action: String,
|
action: String,
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
issueId: Int,
|
issueId: Int,
|
||||||
baseUrl: String,
|
|
||||||
sender: Account
|
sender: Account
|
||||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||||
import WebHookService._
|
import WebHookService._
|
||||||
@@ -404,7 +402,6 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
action: String,
|
action: String,
|
||||||
requestRepository: RepositoryService.RepositoryInfo,
|
requestRepository: RepositoryService.RepositoryInfo,
|
||||||
requestBranch: String,
|
requestBranch: String,
|
||||||
baseUrl: String,
|
|
||||||
sender: Account
|
sender: Account
|
||||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||||
import WebHookService._
|
import WebHookService._
|
||||||
@@ -450,7 +447,6 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
|
|||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
issue: Issue,
|
issue: Issue,
|
||||||
pullRequest: PullRequest,
|
pullRequest: PullRequest,
|
||||||
baseUrl: String,
|
|
||||||
sender: Account
|
sender: Account
|
||||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||||
import WebHookService._
|
import WebHookService._
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
with PrioritiesService
|
with PrioritiesService
|
||||||
with MilestonesService
|
with MilestonesService
|
||||||
with WebHookPullRequestService
|
with WebHookPullRequestService
|
||||||
|
with WebHookPullRequestReviewCommentService
|
||||||
with CommitsService {
|
with CommitsService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||||
@@ -299,7 +300,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||||
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId =>
|
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId =>
|
||||||
getIssue(owner, repository, issueId.toString).foreach { issue =>
|
getIssue(owner, repository, issueId.toString).foreach { issue =>
|
||||||
callIssuesWebHook("closed", repositoryInfo, issue, baseUrl, pusherAccount)
|
callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount)
|
||||||
PluginRegistry().getIssueHooks
|
PluginRegistry().getIssueHooks
|
||||||
.foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount))
|
.foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount))
|
||||||
}
|
}
|
||||||
@@ -319,7 +320,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
}.isDefined) {
|
}.isDefined) {
|
||||||
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
||||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, baseUrl, pusherAccount)
|
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,15 +347,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
command.getType match {
|
command.getType match {
|
||||||
case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE |
|
case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE |
|
||||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD =>
|
ReceiveCommand.Type.UPDATE_NONFASTFORWARD =>
|
||||||
updatePullRequests(owner, repository, branchName)
|
|
||||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||||
callPullRequestWebHookByRequestBranch(
|
updatePullRequests(owner, repository, branchName, pusherAccount, "synchronize")
|
||||||
"synchronize",
|
|
||||||
repositoryInfo,
|
|
||||||
branchName,
|
|
||||||
baseUrl,
|
|
||||||
pusherAccount
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
|||||||
import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService}
|
import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService}
|
||||||
import gitbucket.core.servlet.{CommitLogHook, Database}
|
import gitbucket.core.servlet.{CommitLogHook, Database}
|
||||||
import gitbucket.core.util.{SyntaxSugars, Directory}
|
import gitbucket.core.util.{SyntaxSugars, Directory}
|
||||||
import org.apache.sshd.server.{Command, CommandFactory, Environment, ExitCallback, SessionAware}
|
import org.apache.sshd.server.{Environment, ExitCallback, SessionAware}
|
||||||
|
import org.apache.sshd.server.command.{Command, CommandFactory}
|
||||||
import org.apache.sshd.server.session.ServerSession
|
import org.apache.sshd.server.session.ServerSession
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.{File, InputStream, OutputStream}
|
import java.io.{File, InputStream, OutputStream}
|
||||||
@@ -15,7 +16,7 @@ import org.eclipse.jgit.api.Git
|
|||||||
import Directory._
|
import Directory._
|
||||||
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
||||||
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
||||||
import org.apache.sshd.server.scp.UnknownCommand
|
import org.apache.sshd.server.shell.UnknownCommand
|
||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||||
|
|
||||||
object GitCommand {
|
object GitCommand {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package gitbucket.core.ssh
|
|||||||
|
|
||||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||||
import org.apache.sshd.common.Factory
|
import org.apache.sshd.common.Factory
|
||||||
import org.apache.sshd.server.{Environment, ExitCallback, Command}
|
import org.apache.sshd.server.{Environment, ExitCallback}
|
||||||
|
import org.apache.sshd.server.command.Command
|
||||||
import java.io.{OutputStream, InputStream}
|
import java.io.{OutputStream, InputStream}
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ object SshServer {
|
|||||||
|
|
||||||
private def configure(sshAddress: SshAddress, baseUrl: String) = {
|
private def configure(sshAddress: SshAddress, baseUrl: String) = {
|
||||||
server.setPort(sshAddress.port)
|
server.setPort(sshAddress.port)
|
||||||
val provider = new SimpleGeneratorHostKeyProvider(new File(s"${Directory.GitBucketHome}/gitbucket.ser"))
|
val provider = new SimpleGeneratorHostKeyProvider(
|
||||||
|
java.nio.file.Paths.get(s"${Directory.GitBucketHome}/gitbucket.ser")
|
||||||
|
)
|
||||||
provider.setAlgorithm("RSA")
|
provider.setAlgorithm("RSA")
|
||||||
provider.setOverwriteAllowed(false)
|
provider.setOverwriteAllowed(false)
|
||||||
server.setKeyPairProvider(provider)
|
server.setKeyPairProvider(provider)
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ trait GroupManagerAuthenticator { self: ControllerBase with AccountService =>
|
|||||||
|
|
||||||
private def authenticate(action: => Any) = {
|
private def authenticate(action: => Any) = {
|
||||||
context.loginAccount match {
|
context.loginAccount match {
|
||||||
|
case Some(x) if x.isAdmin => action
|
||||||
|
case Some(x) if x.userName == request.paths(0) => action
|
||||||
case Some(x) if (getGroupMembers(request.paths(0)).exists { member =>
|
case Some(x) if (getGroupMembers(request.paths(0)).exists { member =>
|
||||||
member.userName == x.userName && member.isManager
|
member.userName == x.userName && member.isManager
|
||||||
}) =>
|
}) =>
|
||||||
|
|||||||
35
src/main/scala/gitbucket/core/util/HttpClientUtil.scala
Normal file
35
src/main/scala/gitbucket/core/util/HttpClientUtil.scala
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
import gitbucket.core.service.SystemSettingsService
|
||||||
|
import org.apache.http.HttpHost
|
||||||
|
import org.apache.http.auth.{AuthScope, UsernamePasswordCredentials}
|
||||||
|
import org.apache.http.impl.client.{BasicCredentialsProvider, CloseableHttpClient, HttpClientBuilder}
|
||||||
|
|
||||||
|
object HttpClientUtil {
|
||||||
|
|
||||||
|
def withHttpClient[T](proxy: Option[SystemSettingsService.Proxy])(f: CloseableHttpClient => T): T = {
|
||||||
|
val builder = HttpClientBuilder.create.useSystemProperties
|
||||||
|
|
||||||
|
proxy.foreach { proxy =>
|
||||||
|
builder.setProxy(new HttpHost(proxy.host, proxy.port))
|
||||||
|
|
||||||
|
for (user <- proxy.user; password <- proxy.password) {
|
||||||
|
val credential = new BasicCredentialsProvider()
|
||||||
|
credential.setCredentials(
|
||||||
|
new AuthScope(proxy.host, proxy.port),
|
||||||
|
new UsernamePasswordCredentials(user, password)
|
||||||
|
)
|
||||||
|
builder.setDefaultCredentialsProvider(credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val httpClient = builder.build()
|
||||||
|
|
||||||
|
try {
|
||||||
|
f(httpClient)
|
||||||
|
} finally {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -613,8 +613,12 @@ object JGitUtil {
|
|||||||
df.setRepository(git.getRepository)
|
df.setRepository(git.getRepository)
|
||||||
df.setDiffComparator(RawTextComparator.DEFAULT)
|
df.setDiffComparator(RawTextComparator.DEFAULT)
|
||||||
df.setDetectRenames(true)
|
df.setDetectRenames(true)
|
||||||
df.format(getDiffEntries(git, from, to).head)
|
getDiffEntries(git, from, to)
|
||||||
new String(out.toByteArray, "UTF-8")
|
.map { entry =>
|
||||||
|
df.format(entry)
|
||||||
|
new String(out.toByteArray, "UTF-8")
|
||||||
|
}
|
||||||
|
.mkString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
||||||
@@ -740,15 +744,17 @@ object JGitUtil {
|
|||||||
def getBranchesOfCommit(git: Git, commitId: String): List[String] =
|
def getBranchesOfCommit(git: Git, commitId: String): List[String] =
|
||||||
using(new RevWalk(git.getRepository)) { revWalk =>
|
using(new RevWalk(git.getRepository)) { revWalk =>
|
||||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
||||||
git.getRepository.getAllRefs.entrySet.asScala
|
git.getRepository.getRefDatabase
|
||||||
|
.getRefsByPrefix(Constants.R_HEADS)
|
||||||
|
.asScala
|
||||||
.filter { e =>
|
.filter { e =>
|
||||||
(e.getKey.startsWith(Constants.R_HEADS) && revWalk.isMergedInto(
|
(revWalk.isMergedInto(
|
||||||
commit,
|
commit,
|
||||||
revWalk.parseCommit(e.getValue.getObjectId)
|
revWalk.parseCommit(e.getObjectId)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.map { e =>
|
.map { e =>
|
||||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length)
|
e.getName.substring(Constants.R_HEADS.length)
|
||||||
}
|
}
|
||||||
.toList
|
.toList
|
||||||
.sorted
|
.sorted
|
||||||
@@ -781,15 +787,17 @@ object JGitUtil {
|
|||||||
def getTagsOfCommit(git: Git, commitId: String): List[String] =
|
def getTagsOfCommit(git: Git, commitId: String): List[String] =
|
||||||
using(new RevWalk(git.getRepository)) { revWalk =>
|
using(new RevWalk(git.getRepository)) { revWalk =>
|
||||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
||||||
git.getRepository.getAllRefs.entrySet.asScala
|
git.getRepository.getRefDatabase
|
||||||
|
.getRefsByPrefix(Constants.R_TAGS)
|
||||||
|
.asScala
|
||||||
.filter { e =>
|
.filter { e =>
|
||||||
(e.getKey.startsWith(Constants.R_TAGS) && revWalk.isMergedInto(
|
(revWalk.isMergedInto(
|
||||||
commit,
|
commit,
|
||||||
revWalk.parseCommit(e.getValue.getObjectId)
|
revWalk.parseCommit(e.getObjectId)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.map { e =>
|
.map { e =>
|
||||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length)
|
e.getName.substring(Constants.R_TAGS.length)
|
||||||
}
|
}
|
||||||
.toList
|
.toList
|
||||||
.sorted
|
.sorted
|
||||||
|
|||||||
@@ -127,7 +127,11 @@ object LDAPUtil {
|
|||||||
private def getSslProvider(): Provider = {
|
private def getSslProvider(): Provider = {
|
||||||
val cachedInstance = provider.get()
|
val cachedInstance = provider.get()
|
||||||
if (cachedInstance == null) {
|
if (cachedInstance == null) {
|
||||||
val newInstance = Class.forName("com.sun.net.ssl.internal.ssl.Provider").newInstance().asInstanceOf[Provider]
|
val newInstance = Class
|
||||||
|
.forName("com.sun.net.ssl.internal.ssl.Provider")
|
||||||
|
.getDeclaredConstructor()
|
||||||
|
.newInstance()
|
||||||
|
.asInstanceOf[Provider]
|
||||||
provider.compareAndSet(null, newInstance)
|
provider.compareAndSet(null, newInstance)
|
||||||
newInstance
|
newInstance
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ object SyntaxSugars {
|
|||||||
implicit class HeadValueAccessibleMap(map: Map[String, Seq[String]]) {
|
implicit class HeadValueAccessibleMap(map: Map[String, Seq[String]]) {
|
||||||
def value(key: String): String = map(key).head
|
def value(key: String): String = map(key).head
|
||||||
def optionValue(key: String): Option[String] = map.get(key).flatMap(_.headOption)
|
def optionValue(key: String): Option[String] = map.get(key).flatMap(_.headOption)
|
||||||
def values(key: String): Seq[String] = map.get(key).getOrElse(Seq.empty)
|
def values(key: String): Seq[String] = map.getOrElse(key, Seq.empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,23 @@ package gitbucket.core.view
|
|||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
import gitbucket.core.service.{RepositoryService, RequestCache}
|
import gitbucket.core.service.{RepositoryService, RequestCache}
|
||||||
import gitbucket.core.util.Implicits.RichString
|
import gitbucket.core.util.Implicits.RichString
|
||||||
|
import gitbucket.core.util.StringUtil
|
||||||
|
|
||||||
trait LinkConverter { self: RequestCache =>
|
trait LinkConverter { self: RequestCache =>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a link to the issue or the pull request from the issue id.
|
* Creates a link to the issue or the pull request from the issue id.
|
||||||
*/
|
*/
|
||||||
protected def createIssueLink(repository: RepositoryService.RepositoryInfo, issueId: Int)(
|
protected def createIssueLink(repository: RepositoryService.RepositoryInfo, issueId: Int, title: String)(
|
||||||
implicit context: Context
|
implicit context: Context
|
||||||
): String = {
|
): String = {
|
||||||
val userName = repository.repository.userName
|
val userName = repository.repository.userName
|
||||||
val repositoryName = repository.repository.repositoryName
|
val repositoryName = repository.repository.repositoryName
|
||||||
|
|
||||||
getIssue(userName, repositoryName, issueId.toString) match {
|
getIssue(userName, repositoryName, issueId.toString) match {
|
||||||
case Some(issue) if (issue.isPullRequest) =>
|
case Some(issue) =>
|
||||||
s"""<a href="${context.path}/${userName}/${repositoryName}/pull/${issueId}">Pull #${issueId}</a>"""
|
s"""<a href="${context.path}/${userName}/${repositoryName}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil
|
||||||
case Some(_) =>
|
.escapeHtml(title)}</strong> #${issueId}</a>"""
|
||||||
s"""<a href="${context.path}/${userName}/${repositoryName}/issues/${issueId}">Issue #${issueId}</a>"""
|
|
||||||
case None =>
|
case None =>
|
||||||
s"Unknown #${issueId}"
|
s"Unknown #${issueId}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ object Markdown {
|
|||||||
* Converts Markdown of Wiki pages to HTML.
|
* Converts Markdown of Wiki pages to HTML.
|
||||||
*
|
*
|
||||||
* @param repository the repository which contains the markdown
|
* @param repository the repository which contains the markdown
|
||||||
|
* @param branch the target branch
|
||||||
* @param enableWikiLink if true then wiki style link is available in markdown
|
* @param enableWikiLink if true then wiki style link is available in markdown
|
||||||
* @param enableRefsLink if true then issue reference (e.g. #123) is rendered as link
|
* @param enableRefsLink if true then issue reference (e.g. #123) is rendered as link
|
||||||
* @param enableAnchor if true then anchor for headline is generated
|
* @param enableAnchor if true then anchor for headline is generated
|
||||||
@@ -27,6 +28,7 @@ object Markdown {
|
|||||||
def toHtml(
|
def toHtml(
|
||||||
markdown: String,
|
markdown: String,
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
|
branch: String,
|
||||||
enableWikiLink: Boolean,
|
enableWikiLink: Boolean,
|
||||||
enableRefsLink: Boolean,
|
enableRefsLink: Boolean,
|
||||||
enableAnchor: Boolean,
|
enableAnchor: Boolean,
|
||||||
@@ -45,6 +47,7 @@ object Markdown {
|
|||||||
val renderer = new GitBucketMarkedRenderer(
|
val renderer = new GitBucketMarkedRenderer(
|
||||||
options,
|
options,
|
||||||
repository,
|
repository,
|
||||||
|
branch,
|
||||||
enableWikiLink,
|
enableWikiLink,
|
||||||
enableRefsLink,
|
enableRefsLink,
|
||||||
enableAnchor,
|
enableAnchor,
|
||||||
@@ -62,6 +65,7 @@ object Markdown {
|
|||||||
class GitBucketMarkedRenderer(
|
class GitBucketMarkedRenderer(
|
||||||
options: Options,
|
options: Options,
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
|
branch: String,
|
||||||
enableWikiLink: Boolean,
|
enableWikiLink: Boolean,
|
||||||
enableRefsLink: Boolean,
|
enableRefsLink: Boolean,
|
||||||
enableAnchor: Boolean,
|
enableAnchor: Boolean,
|
||||||
@@ -131,11 +135,11 @@ object Markdown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def link(href: String, title: String, text: String): String = {
|
override def link(href: String, title: String, text: String): String = {
|
||||||
super.link(fixUrl(href, false), title, text)
|
super.link(fixUrl(href, branch, false), title, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def image(href: String, title: String, text: String): String = {
|
override def image(href: String, title: String, text: String): String = {
|
||||||
super.image(fixUrl(href, true), title, text)
|
super.image(fixUrl(href, branch, true), title, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def nolink(text: String): String = {
|
override def nolink(text: String): String = {
|
||||||
@@ -162,7 +166,7 @@ object Markdown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def fixUrl(url: String, isImage: Boolean = false): String = {
|
private def fixUrl(url: String, branch: String, isImage: Boolean = false): String = {
|
||||||
lazy val urlWithRawParam: String = url + (if (isImage && !url.endsWith("?raw=true")) "?raw=true" else "")
|
lazy val urlWithRawParam: String = url + (if (isImage && !url.endsWith("?raw=true")) "?raw=true" else "")
|
||||||
|
|
||||||
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("mailto:") || url.startsWith("/")) {
|
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("mailto:") || url.startsWith("/")) {
|
||||||
@@ -172,13 +176,7 @@ object Markdown {
|
|||||||
} else if (!enableWikiLink) {
|
} else if (!enableWikiLink) {
|
||||||
if (context.currentPath.contains("/blob/")) {
|
if (context.currentPath.contains("/blob/")) {
|
||||||
urlWithRawParam
|
urlWithRawParam
|
||||||
} else if (context.currentPath.contains("/tree/")) {
|
|
||||||
val paths = context.currentPath.split("/")
|
|
||||||
val branch = if (paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch
|
|
||||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
|
|
||||||
} else {
|
} else {
|
||||||
val paths = context.currentPath.split("/")
|
|
||||||
val branch = if (paths.length > 3) paths.last else repository.repository.defaultBranch
|
|
||||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
|
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
def markdown(
|
def markdown(
|
||||||
markdown: String,
|
markdown: String,
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
|
branch: String,
|
||||||
enableWikiLink: Boolean,
|
enableWikiLink: Boolean,
|
||||||
enableRefsLink: Boolean,
|
enableRefsLink: Boolean,
|
||||||
enableLineBreaks: Boolean,
|
enableLineBreaks: Boolean,
|
||||||
@@ -114,6 +115,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
Markdown.toHtml(
|
Markdown.toHtml(
|
||||||
markdown = markdown,
|
markdown = markdown,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = branch,
|
||||||
enableWikiLink = enableWikiLink,
|
enableWikiLink = enableWikiLink,
|
||||||
enableRefsLink = enableRefsLink,
|
enableRefsLink = enableRefsLink,
|
||||||
enableAnchor = enableAnchor,
|
enableAnchor = enableAnchor,
|
||||||
@@ -156,8 +158,10 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
/**
|
/**
|
||||||
* Creates a link to the issue or the pull request from the issue id.
|
* Creates a link to the issue or the pull request from the issue id.
|
||||||
*/
|
*/
|
||||||
def issueLink(repository: RepositoryService.RepositoryInfo, issueId: Int)(implicit context: Context): Html = {
|
def issueLink(repository: RepositoryService.RepositoryInfo, issueId: Int, title: String)(
|
||||||
Html(createIssueLink(repository, issueId))
|
implicit context: Context
|
||||||
|
): Html = {
|
||||||
|
Html(createIssueLink(repository, issueId, title))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,6 +234,12 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m
|
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m
|
||||||
.group(2)}@${m.group(3).substring(0, 7)}</a>"""
|
.group(2)}@${m.group(3).substring(0, 7)}</a>"""
|
||||||
)
|
)
|
||||||
|
.replaceAll(
|
||||||
|
"\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?)\\]",
|
||||||
|
(m: Match) =>
|
||||||
|
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${m
|
||||||
|
.group(3)}</a>"""
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
@gitbucket.core.admin.html.menu("plugins") {
|
@gitbucket.core.admin.html.menu("plugins") {
|
||||||
@gitbucket.core.helper.html.information(info)
|
@gitbucket.core.helper.html.information(info)
|
||||||
<form action="@context.path/admin/plugins/_reload" method="POST" class="pull-right">
|
<form action="@context.path/admin/plugins/_reload" method="POST" class="pull-right">
|
||||||
<input type="checkbox" name="pluginNetworkInstall" id="pluginNetworkInstall" value="true" @if(context.settings.pluginNetworkInstall){checked}>
|
|
||||||
<label for="pluginNetworkInstall">Install plugin from <a href="https://plugins.gitbucket-community.org" target="_blank">plugins.gitbucket-community.org</a></label>
|
|
||||||
<input type="submit" value="Reload plugins" class="btn btn-default">
|
<input type="submit" value="Reload plugins" class="btn btn-default">
|
||||||
</form>
|
</form>
|
||||||
<h1 class="system-settings-title">Plugins</h1>
|
<h1 class="system-settings-title">Plugins</h1>
|
||||||
@@ -42,10 +40,6 @@
|
|||||||
<label class="col-md-2">Version</label>
|
<label class="col-md-2">Version</label>
|
||||||
<span class="col-md-10">@plugin.pluginVersion</span>
|
<span class="col-md-10">@plugin.pluginVersion</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<label class="col-md-2">Name</label>
|
|
||||||
<span class="col-md-10">@plugin.pluginName</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-md-2">Description</label>
|
<label class="col-md-2">Description</label>
|
||||||
<span class="col-md-10 muted">@plugin.description</span>
|
<span class="col-md-10 muted">@plugin.description</span>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<ul class="nav nav-tabs fill-width" id="pullreq-tab">
|
<ul class="nav nav-tabs fill-width" id="pullreq-tab">
|
||||||
<li><a href="#system">System settings</a></li>
|
<li><a href="#system">System settings</a></li>
|
||||||
<li><a href="#authentication">Authentication</a></li>
|
<li><a href="#authentication">Authentication</a></li>
|
||||||
|
<li><a href="#plugins">Plugins</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content fill-width" style="padding-top: 20px;">
|
<div class="tab-content fill-width" style="padding-top: 20px;">
|
||||||
<div class="tab-pane" id="system">
|
<div class="tab-pane" id="system">
|
||||||
@@ -16,6 +17,9 @@
|
|||||||
<div class="tab-pane" id="authentication">
|
<div class="tab-pane" id="authentication">
|
||||||
@settings_authentication(info)
|
@settings_authentication(info)
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane" id="plugins">
|
||||||
|
@settings_plugins(info)
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="align-right" style="margin-top: 20px;">
|
<div class="align-right" style="margin-top: 20px;">
|
||||||
@@ -30,6 +34,9 @@ $(function(){
|
|||||||
if(location.hash == '#authentication'){
|
if(location.hash == '#authentication'){
|
||||||
$('li:has(a[href="#authentication"])').addClass('active');
|
$('li:has(a[href="#authentication"])').addClass('active');
|
||||||
$('div#authentication').addClass('active');
|
$('div#authentication').addClass('active');
|
||||||
|
} else if(location.hash == '#plugins'){
|
||||||
|
$('li:has(a[href="#plugins"])').addClass('active');
|
||||||
|
$('div#plugins').addClass('active');
|
||||||
} else {
|
} else {
|
||||||
$('li:has(a[href="#system"])').addClass('active');
|
$('li:has(a[href="#system"])').addClass('active');
|
||||||
$('div#system').addClass('active');
|
$('div#system').addClass('active');
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||||
|
<label class="strong">Plugin repositories</label>
|
||||||
|
<fieldset>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" id="pluginNetworkInstall" name="pluginNetworkInstall"@if(context.settings.pluginNetworkInstall){ checked} />
|
||||||
|
<a href="https://plugins.gitbucket-community.org" target="_blank">plugins.gitbucket-community.org</a>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<hr>
|
||||||
|
<fieldset>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" id="useProxy" name="useProxy"@if(context.settings.pluginProxy.isDefined){ checked} />
|
||||||
|
Use proxy
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<div class="proxy">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-2" for="proxyHost">Proxy host</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input type="text" id="proxyHost" name="proxy.host" class="form-control" value="@context.settings.pluginProxy.map(_.host)"/>
|
||||||
|
<span id="error-proxy_host" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-2" for="proxyPort">Proxy port</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input type="text" id="proxyPort" name="proxy.port" class="form-control input-mini" value="@context.settings.pluginProxy.map(_.port)"/>
|
||||||
|
<span id="error-proxy_port" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-2" for="proxyUser">Proxy user</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input type="text" id="proxyUser" name="proxy.user" class="form-control" value="@context.settings.pluginProxy.map(_.user)"/>
|
||||||
|
<span id="error-proxy_user" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-2" for="proxyPassword">Proxy password</label>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<input type="password" id="proxyPassword" name="proxy.password" class="form-control" value="@context.settings.pluginProxy.map(_.password)"/>
|
||||||
|
<span id="error-proxy_password" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#useProxy').change(function(){
|
||||||
|
$('.proxy input').prop('disabled', !$(this).prop('checked'));
|
||||||
|
}).change();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -22,12 +22,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="commit-commentContent-@comment.commentId">
|
<div class="commit-commentContent-@comment.commentId">
|
||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = comment.content,
|
markdown = comment.content,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
enableWikiLink = false,
|
branch = repository.repository.defaultBranch,
|
||||||
enableRefsLink = true,
|
enableWikiLink = false,
|
||||||
enableLineBreaks = true,
|
enableRefsLink = true,
|
||||||
enableTaskList = true,
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
hasWritePermission = hasWritePermission
|
hasWritePermission = hasWritePermission
|
||||||
)
|
)
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
commitId: Option[String] = None,
|
commitId: Option[String] = None,
|
||||||
renderScript: Boolean = true)(implicit context: gitbucket.core.controller.Context)
|
renderScript: Boolean = true)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import gitbucket.core.view.helpers
|
@import gitbucket.core.view.helpers
|
||||||
|
@import gitbucket.core.util.StringUtil._
|
||||||
@issueOrPullRequest()={ @if(issue.exists(_.isPullRequest))( "pull request" )else( "issue" ) }
|
@issueOrPullRequest()={ @if(issue.exists(_.isPullRequest))( "pull request" )else( "issue" ) }
|
||||||
@showFormattedComment(comment: gitbucket.core.model.IssueComment)={
|
@showFormattedComment(comment: gitbucket.core.model.IssueComment)={
|
||||||
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
|
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
|
||||||
@@ -32,12 +33,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body markdown-body" id="commentContent-@comment.commentId">
|
<div class="panel-body markdown-body" id="commentContent-@comment.commentId">
|
||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = comment.content,
|
markdown = comment.content,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
enableWikiLink = false,
|
branch = repository.repository.defaultBranch,
|
||||||
enableRefsLink = true,
|
enableWikiLink = false,
|
||||||
enableLineBreaks = true,
|
enableRefsLink = true,
|
||||||
enableTaskList = true,
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
hasWritePermission = isManageable
|
hasWritePermission = isManageable
|
||||||
)
|
)
|
||||||
</div>
|
</div>
|
||||||
@@ -57,12 +59,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body markdown-body" id="issueContent">
|
<div class="panel-body markdown-body" id="issueContent">
|
||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = issue.get.content getOrElse "No description provided.",
|
markdown = issue.get.content getOrElse "No description provided.",
|
||||||
repository = repository,
|
repository = repository,
|
||||||
enableWikiLink = false,
|
branch = repository.repository.defaultBranch,
|
||||||
enableRefsLink = true,
|
enableWikiLink = false,
|
||||||
enableLineBreaks = true,
|
enableRefsLink = true,
|
||||||
enableTaskList = true,
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
hasWritePermission = isManageable
|
hasWritePermission = isManageable
|
||||||
)
|
)
|
||||||
</div>
|
</div>
|
||||||
@@ -112,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="discussion-item-content">
|
<div style="discussion-item-content">
|
||||||
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
|
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
|
||||||
<strong>@helpers.issueLink(repository, issueId.toInt): @rest.mkString(":")</strong>
|
@helpers.issueLink(repository, issueId.toInt, rest.mkString(":"))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,7 +238,7 @@
|
|||||||
<span class="discussion-item-icon"><i class="octicon octicon-pencil"></i></span>
|
<span class="discussion-item-icon"><i class="octicon octicon-pencil"></i></span>
|
||||||
@helpers.avatar(comment.commentedUserName, 16)
|
@helpers.avatar(comment.commentedUserName, 16)
|
||||||
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
@helpers.user(comment.commentedUserName, styleClass="username strong")
|
||||||
change title from <code>@comment.content.split("\r\n")(0)</code> to <code>@comment.content.split("\r\n")(1)</code>
|
change title from <code>@convertLineSeparator(comment.content, "LF").split("\n")(0)</code> to <code>@convertLineSeparator(comment.content, "LF").split("\n")(1)</code>
|
||||||
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -268,12 +271,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body markdown-body commit-commentContent-@comment.commentId">
|
<div class="panel-body markdown-body commit-commentContent-@comment.commentId">
|
||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = comment.content,
|
markdown = comment.content,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
enableWikiLink = false,
|
branch = repository.repository.defaultBranch,
|
||||||
enableRefsLink = true,
|
enableWikiLink = false,
|
||||||
enableLineBreaks = true,
|
enableRefsLink = true,
|
||||||
enableTaskList = true,
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
hasWritePermission = isManageable
|
hasWritePermission = isManageable
|
||||||
)
|
)
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,13 +76,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if(milestone.description.isDefined){
|
@milestone.description.map { description =>
|
||||||
<div class="milestone-description markdown-body">
|
<div class="milestone-description markdown-body">
|
||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = milestone.description.get,
|
markdown = description,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
enableWikiLink = false,
|
branch = repository.repository.defaultBranch,
|
||||||
enableRefsLink = false,
|
enableWikiLink = false,
|
||||||
|
enableRefsLink = false,
|
||||||
enableLineBreaks = true
|
enableLineBreaks = true
|
||||||
)
|
)
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@(title: String,
|
@(title: String,
|
||||||
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||||
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
||||||
members: List[(String, String)],
|
members: List[(String, String, String)],
|
||||||
comments: List[gitbucket.core.model.Comment],
|
comments: List[gitbucket.core.model.Comment],
|
||||||
originId: String,
|
originId: String,
|
||||||
forkedId: String,
|
forkedId: String,
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
<div class="pullreq-info">
|
<div class="pullreq-info">
|
||||||
<div id="compare-edit">
|
<div id="compare-edit">
|
||||||
@gitbucket.core.helper.html.dropdown(originRepository.owner + "/" + originRepository.name, "base fork", filter=("origin_repo", "Find Repository...")) {
|
@gitbucket.core.helper.html.dropdown(originRepository.owner + "/" + originRepository.name, "base fork", filter=("origin_repo", "Find Repository...")) {
|
||||||
@members.map { case (owner, name) =>
|
@members.map { case (owner, name, defaultBranch) =>
|
||||||
<li><a href="#" class="origin-owner" data-owner="@owner" data-name="@name">@gitbucket.core.helper.html.checkicon(owner == originRepository.owner) @owner/@name</a></li>
|
<li><a href="#" class="origin-owner" data-owner="@owner" data-name="@name" data-default-branch="@defaultBranch">@gitbucket.core.helper.html.checkicon(owner == originRepository.owner) @owner/@name</a></li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@gitbucket.core.helper.html.dropdown(originId, "base", filter=("origin_branch", "Find Branch...")) {
|
@gitbucket.core.helper.html.dropdown(originId, "base", filter=("origin_branch", "Find Branch...")) {
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
}
|
}
|
||||||
...
|
...
|
||||||
@gitbucket.core.helper.html.dropdown(forkedRepository.owner + "/" + forkedRepository.name, "head fork", filter=("forked_repo", "Find Repository...")) {
|
@gitbucket.core.helper.html.dropdown(forkedRepository.owner + "/" + forkedRepository.name, "head fork", filter=("forked_repo", "Find Repository...")) {
|
||||||
@members.map { case (owner, name) =>
|
@members.map { case (owner, name, defaultBranch) =>
|
||||||
<li><a href="#" class="forked-owner" data-owner="@owner" data-name="@name">@gitbucket.core.helper.html.checkicon(owner == forkedRepository.owner) @owner/@name</a></li>
|
<li><a href="#" class="forked-owner" data-owner="@owner" data-name="@name" data-default-branch="@defaultBranch">@gitbucket.core.helper.html.checkicon(owner == forkedRepository.owner) @owner/@name</a></li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@gitbucket.core.helper.html.dropdown(forkedId, "compare", filter=("forked_branch", "Find Branch...")) {
|
@gitbucket.core.helper.html.dropdown(forkedId, "compare", filter=("forked_branch", "Find Branch...")) {
|
||||||
@@ -170,25 +170,46 @@
|
|||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('a.origin-owner, a.forked-owner, a.origin-branch, a.forked-branch').click(function(){
|
function updateSelector(e){
|
||||||
var e = $(this);
|
|
||||||
e.parents('ul').find('i').attr('class', 'octicon');
|
e.parents('ul').find('i').attr('class', 'octicon');
|
||||||
e.find('i').addClass('octicon-check');
|
e.find('i').addClass('octicon-check');
|
||||||
e.parents('div.btn-group').find('button span.strong').text(e.text());
|
e.parents('div.btn-group').find('button span.strong').text(e.text());
|
||||||
|
}
|
||||||
|
|
||||||
@if(members.isEmpty){
|
$('a.origin-owner').click(function(){
|
||||||
location.href = '@helpers.url(repository)/compare/' +
|
updateSelector($(this));
|
||||||
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
|
|
||||||
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
|
location.href = '@context.path/' +
|
||||||
} else {
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + '/' +
|
||||||
location.href = '@context.path/' +
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('name')) +'/compare/' +
|
||||||
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + '/' +
|
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
|
||||||
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('name')) +'/compare/' +
|
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('default-branch')) + '...' +
|
||||||
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
|
||||||
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
|
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
|
||||||
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
|
});
|
||||||
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
|
|
||||||
}
|
$('a.forked-owner').click(function(){
|
||||||
|
updateSelector($(this));
|
||||||
|
|
||||||
|
location.href = '@context.path/' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + '/' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('name')) +'/compare/' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('default-branch'));
|
||||||
|
});
|
||||||
|
|
||||||
|
$('a.origin-branch, a.forked-branch').click(function(){
|
||||||
|
updateSelector($(this));
|
||||||
|
|
||||||
|
location.href = '@context.path/' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + '/' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('name')) +'/compare/' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
|
||||||
|
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#show-form').click(function(){
|
$('#show-form').click(function(){
|
||||||
@@ -206,19 +227,12 @@ $(function(){
|
|||||||
function(data){ $('.check-conflict').html(data); });
|
function(data){ $('.check-conflict').html(data); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@if(members.isEmpty){
|
checkConflict(
|
||||||
checkConflict(
|
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ":" +
|
||||||
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')),
|
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')),
|
||||||
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'))
|
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ":" +
|
||||||
);
|
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'))
|
||||||
} else {
|
);
|
||||||
checkConflict(
|
|
||||||
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ":" +
|
|
||||||
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')),
|
|
||||||
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ":" +
|
|
||||||
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -43,7 +43,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<hr>
|
<hr>
|
||||||
@status.conflictMessage.map { message => @helpers.markdown(message, originRepository, false, true, false) }
|
@status.conflictMessage.map { message => @helpers.markdown(
|
||||||
|
markdown = message,
|
||||||
|
repository = originRepository,
|
||||||
|
branch = originRepository.repository.defaultBranch,
|
||||||
|
enableWikiLink = false,
|
||||||
|
enableRefsLink = true,
|
||||||
|
enableLineBreaks = false
|
||||||
|
) }
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
@if(status.branchIsOutOfDate){
|
@if(status.branchIsOutOfDate){
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="box-content" style="line-height: 20pt; margin-bottom: 6px; padding: 10px 6px 10px 10px; background-color: #fff9ea">
|
<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>
|
<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;"
|
<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>
|
href="@helpers.url(repository)/compare/@{parent.owner}:@{helpers.encodeRefName(parent.repository.defaultBranch)}...@{repository.owner}:@{helpers.encodeRefName(branch)}">Compare & pull request</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||||
tag: gitbucket.core.util.JGitUtil.TagInfo,
|
tag: gitbucket.core.util.JGitUtil.TagInfo,
|
||||||
|
tags: Seq[String],
|
||||||
|
content: String,
|
||||||
release: Option[(gitbucket.core.model.ReleaseTag, Seq[gitbucket.core.model.ReleaseAsset])])(implicit context: gitbucket.core.controller.Context)
|
release: Option[(gitbucket.core.model.ReleaseTag, Seq[gitbucket.core.model.ReleaseAsset])])(implicit context: gitbucket.core.controller.Context)
|
||||||
@import gitbucket.core.view.helpers
|
@import gitbucket.core.view.helpers
|
||||||
@gitbucket.core.html.main(s"New Release - ${repository.owner}/${repository.name}", Some(repository)){
|
@gitbucket.core.html.main(s"New Release - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@@ -14,9 +16,18 @@
|
|||||||
}
|
}
|
||||||
<span id="error-name" class="error"></span>
|
<span id="error-name" class="error"></span>
|
||||||
<input type="text" id="release-name" name="name" class="form-control" value="@release.map { case (release, _) => @release.name }.getOrElse(tag.name)" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
|
<input type="text" id="release-name" name="name" class="form-control" value="@release.map { case (release, _) => @release.name }.getOrElse(tag.name)" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
|
||||||
|
<div class="pull-right">
|
||||||
|
Previous tag: <select id="insert-changelog-tag">
|
||||||
|
@tags.map { tag =>
|
||||||
|
<option value="@tag">@tag</option>
|
||||||
|
}
|
||||||
|
<option value="">None</option>
|
||||||
|
</select>
|
||||||
|
<input id="insert-changelog-button" type="button" value="Insert ChangeLog"/>
|
||||||
|
</div>
|
||||||
@gitbucket.core.helper.html.preview(
|
@gitbucket.core.helper.html.preview(
|
||||||
repository = repository,
|
repository = repository,
|
||||||
content = release.flatMap { case (release, _) => release.content }.getOrElse(tag.message),
|
content = content,
|
||||||
enableWikiLink = false,
|
enableWikiLink = false,
|
||||||
enableRefsLink = true,
|
enableRefsLink = true,
|
||||||
enableLineBreaks = true,
|
enableLineBreaks = true,
|
||||||
@@ -74,5 +85,19 @@ $(function(){
|
|||||||
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
|
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#insert-changelog-button').click(function(){
|
||||||
|
var previousTag = $('#insert-changelog-tag option:selected').val();
|
||||||
|
$.get('@context.path/@repository.owner/@repository.name/changelog/' + encodeURIComponent(previousTag) + '...@helpers.urlEncode(tag.name)', function(data){
|
||||||
|
console.log(data);
|
||||||
|
var content = $('textarea[name=content]').val();
|
||||||
|
if(content == ''){
|
||||||
|
content = data;
|
||||||
|
} else {
|
||||||
|
content = content.trimRight() + '\n\n' + data;
|
||||||
|
}
|
||||||
|
$('textarea[name=content]').val(content);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = release.content getOrElse "No description provided.",
|
markdown = release.content getOrElse "No description provided.",
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = repository.repository.defaultBranch,
|
||||||
enableWikiLink = false,
|
enableWikiLink = false,
|
||||||
enableRefsLink = true,
|
enableRefsLink = true,
|
||||||
enableLineBreaks = true,
|
enableLineBreaks = true,
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
@if(hasWritePermission){
|
@if(hasWritePermission){
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a class="btn btn-success" href="@helpers.url(repository)/releases/@{helpers.encodeRefName(tag.name)}/create" id="edit">Create release</a>
|
<a class="btn btn-success" href="@helpers.url(repository)/releases/@{helpers.urlEncode(tag.name)}/create" id="edit">Create release</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div>@tag.message<br><br></div>
|
<div>@tag.message<br><br></div>
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
@release.map { case (release, assets) =>
|
@release.map { case (release, assets) =>
|
||||||
@assets.map { asset =>
|
@assets.map { asset =>
|
||||||
<li>
|
<li>
|
||||||
<i class="octicon octicon-file"></i><a href="@helpers.url(repository)/releases/@release.tag/assets/@asset.fileName">@asset.label</a>
|
<i class="octicon octicon-file"></i><a href="@helpers.url(repository)/releases/@helpers.urlEncode(release.tag)/assets/@asset.fileName">@asset.label</a>
|
||||||
<span class="label label-default">@helpers.readableSize(Some(asset.size))</span>
|
<span class="label label-default">@helpers.readableSize(Some(asset.size))</span>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = release.content getOrElse "No description provided.",
|
markdown = release.content getOrElse "No description provided.",
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = repository.repository.defaultBranch,
|
||||||
enableWikiLink = false,
|
enableWikiLink = false,
|
||||||
enableRefsLink = true,
|
enableRefsLink = true,
|
||||||
enableLineBreaks = true,
|
enableLineBreaks = true,
|
||||||
|
|||||||
@@ -55,24 +55,24 @@
|
|||||||
<span class="label label-info">LFS</span>
|
<span class="label label-info">LFS</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="box-header">
|
<div class="box-header" style="line-height: 28px;">
|
||||||
@helpers.avatarLink(latestCommit, 28)
|
@helpers.avatarLink(latestCommit, 20)
|
||||||
@helpers.user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
|
@helpers.user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
|
||||||
<span class="muted">@gitbucket.core.helper.html.datetimeago(latestCommit.commitTime)</span>
|
<span class="muted">@gitbucket.core.helper.html.datetimeago(latestCommit.commitTime)</span>
|
||||||
<span class="label label-default">@helpers.readableSize(content.size)</span>
|
<span class="label label-default">@helpers.readableSize(content.size)</span>
|
||||||
<a href="@helpers.url(repository)/commit/@latestCommit.id" class="commit-message">@helpers.link(latestCommit.summary, repository)</a>
|
<a href="@helpers.url(repository)/commit/@latestCommit.id" class="commit-message">@helpers.link(latestCommit.summary, repository)</a>
|
||||||
<div class="btn-group pull-right">
|
<div class="btn-group pull-right">
|
||||||
@if(hasWritePermission && content.viewType == "text" && repository.branchList.contains(branch)){
|
|
||||||
<a class="btn btn-sm btn-default" href="@helpers.url(repository)/edit/@helpers.encodeRefName((branch :: pathList).mkString("/"))">Edit</a>
|
|
||||||
}
|
|
||||||
<a class="btn btn-sm btn-default" href="@helpers.url(repository)/raw/@latestCommit.id/@helpers.encodeRefName(pathList.mkString("/"))">Raw</a>
|
<a class="btn btn-sm btn-default" href="@helpers.url(repository)/raw/@latestCommit.id/@helpers.encodeRefName(pathList.mkString("/"))">Raw</a>
|
||||||
@if(content.viewType == "text"){
|
@if(content.viewType == "text"){
|
||||||
<a class="btn btn-sm btn-default blame-action" href="@helpers.url(repository)/blame/@latestCommit.id/@helpers.encodeRefName(pathList.mkString("/"))"
|
<a class="btn btn-sm btn-default blame-action" href="@helpers.url(repository)/blame/@latestCommit.id/@helpers.encodeRefName(pathList.mkString("/"))"
|
||||||
data-url="@helpers.url(repository)/get-blame/@helpers.encodeRefName((latestCommit.id :: pathList).mkString("/"))" data-repository="@helpers.url(repository)">Blame</a>
|
data-url="@helpers.url(repository)/get-blame/@helpers.encodeRefName((latestCommit.id :: pathList).mkString("/"))" data-repository="@helpers.url(repository)">Blame</a>
|
||||||
}
|
}
|
||||||
<a class="btn btn-sm btn-default" href="@helpers.url(repository)/commits/@helpers.encodeRefName((branch :: pathList).mkString("/"))">History</a>
|
<a class="btn btn-sm btn-default" href="@helpers.url(repository)/commits/@helpers.encodeRefName((branch :: pathList).mkString("/"))">History</a>
|
||||||
|
@if(hasWritePermission && content.viewType == "text" && repository.branchList.contains(branch)){
|
||||||
|
<a class="btn btn-sm" style="padding-right: 4px;" href="@helpers.url(repository)/edit/@helpers.encodeRefName((branch :: pathList).mkString("/"))"><i class="octicon octicon-pencil"></i></a>
|
||||||
|
}
|
||||||
@if(hasWritePermission && repository.branchList.contains(branch)){
|
@if(hasWritePermission && repository.branchList.contains(branch)){
|
||||||
<a class="btn btn-sm btn-danger" href="@helpers.url(repository)/remove/@helpers.encodeRefName((branch :: pathList).mkString("/"))">Delete</a>
|
<a class="btn btn-sm" style="padding-right: 4px;" href="@helpers.url(repository)/remove/@helpers.encodeRefName((branch :: pathList).mkString("/"))"><i class="octicon octicon-trashcan"></i></a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -196,11 +196,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</table>
|
</table>
|
||||||
@readme.map { case(filePath, content) =>
|
@readme.map { case (filePath, content) =>
|
||||||
<div id="readme" class="panel panel-default">
|
<div id="readme" class="box-header">
|
||||||
<div class="panel-heading strong">@filePath.last</div>
|
<div class="strong" style="line-height: 28px;">
|
||||||
<div class="panel-body markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div>
|
<i class="octicon octicon-file"></i>
|
||||||
|
@filePath.last
|
||||||
|
@if(hasWritePermission){
|
||||||
|
<div class="btn-group pull-right">
|
||||||
|
<a class="btn btn-sm" style="padding-right: 4px;" href="@helpers.url(repository)/edit/@helpers.encodeRefName((branch :: filePath).mkString("/"))"><i class="octicon octicon-pencil"></i></a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="box-content-bottom markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,5 +50,15 @@ $(function(){
|
|||||||
$('#delete-form').submit(function(){
|
$('#delete-form').submit(function(){
|
||||||
return confirm('Once you delete a repository, there is no going back.\nAre you sure?');
|
return confirm('Once you delete a repository, there is no going back.\nAre you sure?');
|
||||||
});
|
});
|
||||||
|
$('#transfer-form').submit(function(){
|
||||||
|
if($('#transfer-form').data('validated') === true){
|
||||||
|
return confirm('Transfer to the repository owner you entered.\nAre you sure?');
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#gc-form').submit(function(){
|
||||||
|
return confirm('The garbage collection may take a long time.\nDo you want to execute it?');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ $(function(){
|
|||||||
error:function (e) {
|
error:function (e) {
|
||||||
if(e) {
|
if(e) {
|
||||||
console.log(e.responseText, e);
|
console.log(e.responseText, e);
|
||||||
alert("request error ( http status " + e.status + " error on gitbugket or browser to gitbucket. show details on your javascript console )");
|
alert("request error ( http status " + e.status + " error on gitbucket or browser to gitbucket. show details on your javascript console )");
|
||||||
}else{
|
}else{
|
||||||
alert("unknown javascript error (please report to gitbucket team)");
|
alert("unknown javascript error (please report to gitbucket team)");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
@if(isEditable){
|
@if(isEditable){
|
||||||
<a href="@helpers.url(repository)/wiki/_Sidebar/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
<a href="@helpers.url(repository)/wiki/_Sidebar/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||||
}
|
}
|
||||||
@helpers.markdown(sidebarPage.content, repository, true, false, false, false, pages)
|
@helpers.markdown(sidebarPage.content, repository, "master", true, false, false, false, pages)
|
||||||
</div>
|
</div>
|
||||||
}.getOrElse{
|
}.getOrElse{
|
||||||
@if(isEditable){
|
@if(isEditable){
|
||||||
@@ -85,6 +85,7 @@
|
|||||||
@helpers.markdown(
|
@helpers.markdown(
|
||||||
markdown = page.content,
|
markdown = page.content,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
|
branch = "master",
|
||||||
enableWikiLink = true,
|
enableWikiLink = true,
|
||||||
enableRefsLink = false,
|
enableRefsLink = false,
|
||||||
enableLineBreaks = false,
|
enableLineBreaks = false,
|
||||||
@@ -98,7 +99,16 @@
|
|||||||
@if(isEditable){
|
@if(isEditable){
|
||||||
<a href="@helpers.url(repository)/wiki/_Footer/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
<a href="@helpers.url(repository)/wiki/_Footer/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||||
}
|
}
|
||||||
@helpers.markdown(footerPage.content, repository, true, false, false, false, pages)
|
@helpers.markdown(
|
||||||
|
markdown = footerPage.content,
|
||||||
|
repository = repository,
|
||||||
|
branch = "master",
|
||||||
|
enableWikiLink = true,
|
||||||
|
enableRefsLink = false,
|
||||||
|
enableLineBreaks = false,
|
||||||
|
enableAnchor = false,
|
||||||
|
pages = pages
|
||||||
|
)
|
||||||
</div>
|
</div>
|
||||||
}.getOrElse{
|
}.getOrElse{
|
||||||
@if(isEditable){
|
@if(isEditable){
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class GitBucketCoreModuleSpec extends FunSuite {
|
|||||||
.withPort(3306)
|
.withPort(3306)
|
||||||
.withUser("sa", "sa")
|
.withUser("sa", "sa")
|
||||||
.withCharset(Charset.UTF8)
|
.withCharset(Charset.UTF8)
|
||||||
.withServerVariable("log_syslog", 0)
|
|
||||||
.withServerVariable("bind-address", "127.0.0.1")
|
.withServerVariable("bind-address", "127.0.0.1")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package gitbucket.core.service
|
|||||||
import gitbucket.core.model._
|
import gitbucket.core.model._
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile._
|
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
|
|
||||||
class AccessTokenServiceSpec extends FunSuite with ServiceSpecBase {
|
class AccessTokenServiceSpec extends FunSuite with ServiceSpecBase {
|
||||||
@@ -53,6 +52,7 @@ class AccessTokenServiceSpec extends FunSuite with ServiceSpecBase {
|
|||||||
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
|
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
|
||||||
assert(AccessTokenService.getAccountByAccessToken(token) match {
|
assert(AccessTokenService.getAccountByAccessToken(token) match {
|
||||||
case Some(user) => user.userName == "root"
|
case Some(user) => user.userName == "root"
|
||||||
|
case _ => fail()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +88,7 @@ class AccessTokenServiceSpec extends FunSuite with ServiceSpecBase {
|
|||||||
|
|
||||||
assert(AccessTokenService.getAccountByAccessToken(token) match {
|
assert(AccessTokenService.getAccountByAccessToken(token) match {
|
||||||
case Some(user) => user.userName == "user3"
|
case Some(user) => user.userName == "user3"
|
||||||
|
case _ => fail()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import org.scalatest.FunSpec
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class MergeServiceSpec extends FunSpec {
|
class MergeServiceSpec extends FunSpec {
|
||||||
val service = new MergeService {}
|
val service = new MergeService with AccountService with ActivityService with IssuesService with LabelsService
|
||||||
|
with MilestonesService with RepositoryService with PrioritiesService with PullRequestService with CommitsService
|
||||||
|
with WebHookPullRequestService with WebHookPullRequestReviewCommentService {}
|
||||||
val branch = "master"
|
val branch = "master"
|
||||||
val issueId = 10
|
val issueId = 10
|
||||||
def initRepository(owner: String, name: String): File = {
|
def initRepository(owner: String, name: String): File = {
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ class PullRequestServiceSpec
|
|||||||
with PullRequestService
|
with PullRequestService
|
||||||
with IssuesService
|
with IssuesService
|
||||||
with AccountService
|
with AccountService
|
||||||
|
with ActivityService
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
with CommitsService
|
with CommitsService
|
||||||
with LabelsService
|
with LabelsService
|
||||||
with MilestonesService
|
with MilestonesService
|
||||||
with PrioritiesService {
|
with PrioritiesService
|
||||||
|
with WebHookService
|
||||||
|
with WebHookPullRequestService
|
||||||
|
with WebHookPullRequestReviewCommentService {
|
||||||
|
|
||||||
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)
|
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ trait ServiceSpecBase {
|
|||||||
|
|
||||||
def user(name: String)(implicit s: Session): Account = AccountService.getAccountByUserName(name).get
|
def user(name: String)(implicit s: Session): Account = AccountService.getAccountByUserName(name).get
|
||||||
|
|
||||||
lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService
|
lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService
|
||||||
with CommitsService with CommitStatusService with LabelsService with MilestonesService with PrioritiesService() {}
|
with PullRequestService with CommitsService with CommitStatusService with LabelsService with MilestonesService
|
||||||
|
with PrioritiesService with WebHookService with WebHookPullRequestService
|
||||||
|
with WebHookPullRequestReviewCommentService {}
|
||||||
|
|
||||||
def generateNewUserWithDBRepository(userName: String, repositoryName: String)(implicit s: Session): Account = {
|
def generateNewUserWithDBRepository(userName: String, repositoryName: String)(implicit s: Session): Account = {
|
||||||
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))
|
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import org.scalatest.FunSuite
|
|||||||
import gitbucket.core.model.WebHookContentType
|
import gitbucket.core.model.WebHookContentType
|
||||||
|
|
||||||
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
||||||
lazy val service = new WebHookPullRequestService with AccountService with RepositoryService with PullRequestService
|
lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService
|
||||||
with IssuesService with CommitsService with LabelsService with MilestonesService with PrioritiesService
|
with PullRequestService with IssuesService with CommitsService with LabelsService with MilestonesService
|
||||||
|
with PrioritiesService with WebHookPullRequestReviewCommentService
|
||||||
|
|
||||||
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
|
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
|
||||||
withTestDB { implicit session =>
|
withTestDB { implicit session =>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package gitbucket.core.ssh
|
package gitbucket.core.ssh
|
||||||
|
|
||||||
import org.apache.sshd.server.scp.UnknownCommand
|
import org.apache.sshd.server.shell.UnknownCommand
|
||||||
import org.scalatest.FunSpec
|
import org.scalatest.FunSpec
|
||||||
|
|
||||||
class GitCommandFactorySpec extends FunSpec {
|
class GitCommandFactorySpec extends FunSpec {
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
|
|||||||
oidc = None,
|
oidc = None,
|
||||||
skinName = "skin-blue",
|
skinName = "skin-blue",
|
||||||
showMailAddress = false,
|
showMailAddress = false,
|
||||||
pluginNetworkInstall = false
|
pluginNetworkInstall = false,
|
||||||
|
pluginProxy = None
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user