mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-10-30 01:56:09 +01:00
Compare commits
264 Commits
4.35.2
...
optimize-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cc3a0d36e | ||
|
|
7f665c649b | ||
|
|
dcbadb4071 | ||
|
|
e3096d15ff | ||
|
|
a83c24e7b3 | ||
|
|
73457c9658 | ||
|
|
cfc8d9f3f1 | ||
|
|
8f423b83ea | ||
|
|
1e7ac532b6 | ||
|
|
0fd1db4596 | ||
|
|
0755b7ab7f | ||
|
|
052382e5c4 | ||
|
|
f44a63cec1 | ||
|
|
603d67354a | ||
|
|
aafa423b9f | ||
|
|
2da9d0a801 | ||
|
|
b60c112a74 | ||
|
|
843ed6df37 | ||
|
|
0f0a849677 | ||
|
|
682901ccbb | ||
|
|
048fdb8837 | ||
|
|
3353616789 | ||
|
|
b6cb4c865f | ||
|
|
1fcfd093f7 | ||
|
|
3f27c6e733 | ||
|
|
b6bd9bfc3b | ||
|
|
6c392f0056 | ||
|
|
9a38de9a23 | ||
|
|
8883600090 | ||
|
|
ab822a3c27 | ||
|
|
0e4d64de23 | ||
|
|
fbc6bd36bd | ||
|
|
ed90ca2dce | ||
|
|
537ef92149 | ||
|
|
d51afa7d40 | ||
|
|
975cffff48 | ||
|
|
d92e9c00e8 | ||
|
|
12d72cbb19 | ||
|
|
d8e03bed1f | ||
|
|
f48c087cd8 | ||
|
|
917663e0df | ||
|
|
556ddbc926 | ||
|
|
1c6f37b8e8 | ||
|
|
720a329a50 | ||
|
|
220a8f076a | ||
|
|
43be8333c7 | ||
|
|
08706ab4df | ||
|
|
b1196657e0 | ||
|
|
334bd0c919 | ||
|
|
cf0f896972 | ||
|
|
d21ca3ff8a | ||
|
|
83f1f16de7 | ||
|
|
0fa2ccf107 | ||
|
|
18e3dd431b | ||
|
|
f25dee2249 | ||
|
|
575ffa9580 | ||
|
|
f17af5aeb0 | ||
|
|
639f153589 | ||
|
|
fb07098c13 | ||
|
|
74fc08b039 | ||
|
|
2776e00004 | ||
|
|
5932fce303 | ||
|
|
39c9fc4261 | ||
|
|
89ea4509a3 | ||
|
|
d19d838ead | ||
|
|
633a2699a8 | ||
|
|
e79bca4a3c | ||
|
|
f8ab480d20 | ||
|
|
a14129e340 | ||
|
|
5ec39df6e0 | ||
|
|
956e0c6321 | ||
|
|
0660a9203a | ||
|
|
1b660272a1 | ||
|
|
d9ef9b874d | ||
|
|
7824f796ee | ||
|
|
838a8d4c7b | ||
|
|
8db9f77f91 | ||
|
|
4f82a469d9 | ||
|
|
0f6ae8559b | ||
|
|
3f4b2eec35 | ||
|
|
a020601d3a | ||
|
|
d511205588 | ||
|
|
fc896b2ea1 | ||
|
|
3819670535 | ||
|
|
067669a18c | ||
|
|
bef7cee8db | ||
|
|
cb9f56fc22 | ||
|
|
719a521f06 | ||
|
|
413754976f | ||
|
|
4530cfc068 | ||
|
|
fcc7d6da2d | ||
|
|
33e565d029 | ||
|
|
6a1da37e9a | ||
|
|
897cc5eea4 | ||
|
|
5cbea281af | ||
|
|
e74db57b75 | ||
|
|
6b2d124d09 | ||
|
|
7b3c77db8c | ||
|
|
1bcab5c2c7 | ||
|
|
16020e9a7d | ||
|
|
dfd3e22986 | ||
|
|
48d7a20fb7 | ||
|
|
04108a0a1a | ||
|
|
e0bab59846 | ||
|
|
1004c83bc4 | ||
|
|
502bb450e3 | ||
|
|
fb03b0bec7 | ||
|
|
2c3a1c9c92 | ||
|
|
73b7ee2d57 | ||
|
|
71df890f39 | ||
|
|
1fdac404b1 | ||
|
|
47be1e60bb | ||
|
|
1c05f50954 | ||
|
|
ce26a48fd7 | ||
|
|
26e0838ae0 | ||
|
|
6b5b0db20a | ||
|
|
5973c8cb85 | ||
|
|
f683439c73 | ||
|
|
625f3655ec | ||
|
|
4a5ab1ae71 | ||
|
|
3bcae2c6d2 | ||
|
|
4441eff657 | ||
|
|
9fed83e222 | ||
|
|
42ea9bac65 | ||
|
|
2aba827772 | ||
|
|
31e84bd543 | ||
|
|
ba471b8bf7 | ||
|
|
90ec95494d | ||
|
|
e72dbad765 | ||
|
|
ed63ea4674 | ||
|
|
559a52404f | ||
|
|
5e80848524 | ||
|
|
055a4f0798 | ||
|
|
6f432e537c | ||
|
|
a2abbbbd63 | ||
|
|
16707b99e6 | ||
|
|
80a4ed62cc | ||
|
|
b713a334f0 | ||
|
|
ec6264c04c | ||
|
|
44bb1ab565 | ||
|
|
523ee025ac | ||
|
|
d193c6399c | ||
|
|
8c9715f4d9 | ||
|
|
3bbdcd5f14 | ||
|
|
c183d3b0b5 | ||
|
|
b6f0c6bf57 | ||
|
|
8719e834f3 | ||
|
|
f23b8c6d61 | ||
|
|
d46be31ea8 | ||
|
|
9bb36e4cb2 | ||
|
|
2780fb9605 | ||
|
|
b69e2d299a | ||
|
|
a07bd7b1a4 | ||
|
|
d605455678 | ||
|
|
781b1169a2 | ||
|
|
80112d3437 | ||
|
|
131af7f4fa | ||
|
|
1ceac4ef9f | ||
|
|
b7aa3e69f4 | ||
|
|
62231c41b2 | ||
|
|
6c0a33712b | ||
|
|
e192624489 | ||
|
|
68609e44de | ||
|
|
c8e3af6072 | ||
|
|
9f7048a19c | ||
|
|
cbdc0528ca | ||
|
|
444facc95b | ||
|
|
ed40ddaa45 | ||
|
|
43c629b3e5 | ||
|
|
0d2c156579 | ||
|
|
ab218f8540 | ||
|
|
4b8a375890 | ||
|
|
9c3a5f3f0a | ||
|
|
1bcfbec29c | ||
|
|
781940436a | ||
|
|
7f226a6ab1 | ||
|
|
f538950d64 | ||
|
|
142be65893 | ||
|
|
8b01e5bc1f | ||
|
|
bde0aab207 | ||
|
|
31ecfef1c9 | ||
|
|
0fdae4c334 | ||
|
|
3ea7de974b | ||
|
|
34a7990d1d | ||
|
|
709d759d09 | ||
|
|
2d47252b27 | ||
|
|
8586e141fc | ||
|
|
e3887e6872 | ||
|
|
fd658c35ee | ||
|
|
ca4c1b237f | ||
|
|
92b17fd719 | ||
|
|
b4453531a3 | ||
|
|
de72a9223f | ||
|
|
ae26e3031e | ||
|
|
c26b41cfc2 | ||
|
|
2c4ee664ab | ||
|
|
911cd45965 | ||
|
|
320cc58c62 | ||
|
|
cfa1638e3c | ||
|
|
27264bf59a | ||
|
|
935283272e | ||
|
|
ea440294b1 | ||
|
|
df8435bf6a | ||
|
|
4fe019b11f | ||
|
|
538c79705a | ||
|
|
e94d972741 | ||
|
|
c58a9aca6a | ||
|
|
a6612e8522 | ||
|
|
bd455b9a0a | ||
|
|
122853f1f6 | ||
|
|
debbc21bf3 | ||
|
|
d991fdcbd9 | ||
|
|
78cc8f14fd | ||
|
|
09f1a06267 | ||
|
|
6fb910a939 | ||
|
|
9e891a0168 | ||
|
|
0bc77ce453 | ||
|
|
a46c821673 | ||
|
|
cae843d18f | ||
|
|
40ffc689b0 | ||
|
|
65afbd25b8 | ||
|
|
458b30610f | ||
|
|
a89608a2e2 | ||
|
|
0f45311d5e | ||
|
|
447ed6ce88 | ||
|
|
ab358a10bc | ||
|
|
5148851d49 | ||
|
|
068ecd30fe | ||
|
|
5396c0ee58 | ||
|
|
2328c1878b | ||
|
|
bc9a6f464a | ||
|
|
4613fd4cb4 | ||
|
|
ff89c8805c | ||
|
|
95d98c525b | ||
|
|
bc78a38570 | ||
|
|
8db2398dd8 | ||
|
|
e073ca74da | ||
|
|
50e899bf83 | ||
|
|
5ddd6234e3 | ||
|
|
84fa11b5df | ||
|
|
f51db60fc7 | ||
|
|
351e72e8c2 | ||
|
|
a0f54b0b8e | ||
|
|
4c491e40c7 | ||
|
|
54440d9cde | ||
|
|
ea8333844c | ||
|
|
fc34df7008 | ||
|
|
d49d62edfc | ||
|
|
246e2e2de4 | ||
|
|
83b48d26ce | ||
|
|
3d40094925 | ||
|
|
2d7e8526ed | ||
|
|
7768f4c4dd | ||
|
|
e43fabf895 | ||
|
|
7cf83979a6 | ||
|
|
1ecc30c570 | ||
|
|
ee0721f40d | ||
|
|
07836edf84 | ||
|
|
bfbe40e027 | ||
|
|
842d3b6185 | ||
|
|
086437ca4f | ||
|
|
6e48435f38 | ||
|
|
3b8e903cb5 | ||
|
|
55308f2deb |
4
.github/CODEOWNERS
vendored
Normal file
4
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
* @takezoe
|
||||
|
||||
build.sbt @xuwei-k
|
||||
project/* @xuwei-k
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -12,7 +12,7 @@
|
||||
**Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
||||
|
||||
**Problem description**:
|
||||
- *be as explicit has you can*
|
||||
- *be as explicit as you can*
|
||||
- *describe the problem and its symptoms*
|
||||
- *explain how to reproduce*
|
||||
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
||||
|
||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -5,13 +5,14 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
java: [8, 11]
|
||||
java: [8, 11, 17]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v2.1.6
|
||||
env:
|
||||
cache-name: cache-sbt-libs
|
||||
with:
|
||||
@@ -21,11 +22,14 @@ jobs:
|
||||
~/.cache/coursier/v1
|
||||
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: adopt
|
||||
- name: Run tests
|
||||
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
||||
- name: Scala 3
|
||||
run: sbt '++ 3.0.1!' update # TODO
|
||||
- name: Build executable
|
||||
run: sbt executable
|
||||
- name: Upload artifacts
|
||||
|
||||
7
.scala-steward.conf
Normal file
7
.scala-steward.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
updates.limit = 3
|
||||
|
||||
updates.includeScala = true
|
||||
|
||||
updates.pin = [
|
||||
{ groupId = "org.eclipse.jetty", version = "9." }
|
||||
]
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,6 +1,22 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.36.2 - 16 Aug 2021
|
||||
- Escape user name in avatar image tag
|
||||
|
||||
### 4.36.1 - 22 Jul 2021
|
||||
- Bump gitbucket-gist-plugin to 4.21.0
|
||||
|
||||
### 4.36.0 - 17 Jul 2021
|
||||
- Tag selector in the repository viewer
|
||||
- Link issues/pull requests of other repositories
|
||||
- Files and lines can be linked in the diff view
|
||||
- Option to disable XSS protection
|
||||
|
||||
### 4.35.3 - 14 Jan 2021
|
||||
- Fix a bug that Wiki page cannot be deleted
|
||||
- Fix a deployment issue on Tomcat
|
||||
|
||||
### 4.35.2 - 30 Dec 2020
|
||||
- Upgrade gitbucket-notifications-plugin to 1.10.0
|
||||
- Upgrade oauth2-oidc-sdk to 8.29.1 to solve dependency issue
|
||||
|
||||
41
README.md
41
README.md
@@ -8,6 +8,8 @@ GitBucket is a Git web platform powered by Scala offering:
|
||||
- High extensibility by plugins
|
||||
- API compatibility with GitHub
|
||||
|
||||

|
||||
|
||||
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
|
||||
|
||||
Features
|
||||
@@ -22,8 +24,6 @@ The current version of GitBucket provides many features such as:
|
||||
- Account and group management with LDAP integration
|
||||
- a Plug-in system
|
||||
|
||||
If you want to try the development version of GitBucket, see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/readme.md).
|
||||
|
||||
Installation
|
||||
--------
|
||||
GitBucket requires **Java8**. You have to install it, if it is not already installed.
|
||||
@@ -48,34 +48,31 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
|
||||
|
||||
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
||||
|
||||
Building and Development
|
||||
-----------
|
||||
If you want to try the development version of GitBucket, or want to contribute to the project, please see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/readme.md).
|
||||
It provides instructions on building from source and on setting up an IDE for debugging.
|
||||
It also contains documentation of the core concepts used within the project.
|
||||
|
||||
Support
|
||||
--------
|
||||
|
||||
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||
- 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 our [Gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.35.x
|
||||
What's New in 4.36.x
|
||||
-------------
|
||||
### 4.35.2 - 30 Dec 2020
|
||||
- Upgrade gitbucket-notifications-plugin to 1.10.0
|
||||
- Upgrade oauth2-oidc-sdk to 8.29.1 to solve dependency issue
|
||||
### 4.36.2 - 16 Aug 2021
|
||||
- Escape user name in avatar image tag
|
||||
|
||||
### 4.35.1 - 29 Dec 2020
|
||||
### 4.36.1 - 22 Jul 2021
|
||||
- Bump gitbucket-gist-plugin to 4.21.0
|
||||
|
||||
- Fix database migration issue which happens if webhook is configured
|
||||
- Call push webhook when pull request is merged
|
||||
- Show commit status at commits tab of pull request
|
||||
|
||||
### 4.35.0 - 25 Dec 2020
|
||||
|
||||
- Editor and source viewer color theme
|
||||
- Auto completion for issues and pull requests
|
||||
- Upload image from clipboard
|
||||
- Close multiple issues by commit comment
|
||||
- Create pull request from online editor
|
||||
- Milestone overview
|
||||
- Commit status at various places
|
||||
- WebAPI coverage improvements
|
||||
### 4.36.0 - 17 Jul 2021
|
||||
- Tag selector in the repository viewer
|
||||
- Link issues/pull requests of other repositories
|
||||
- Files and lines can be linked in the diff view
|
||||
- Option to disable XSS protection
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
120
build.sbt
120
build.sbt
@@ -1,23 +1,21 @@
|
||||
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
|
||||
import com.typesafe.sbt.pgp.PgpKeys._
|
||||
import com.jsuereth.sbtpgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.35.2"
|
||||
val ScalatraVersion = "2.7.1"
|
||||
val JettyVersion = "9.4.32.v20200930"
|
||||
val JgitVersion = "5.9.0.202009080501-r"
|
||||
val GitBucketVersion = "4.36.2"
|
||||
val ScalatraVersion = "2.8.2"
|
||||
val JettyVersion = "9.4.44.v20210927"
|
||||
val JgitVersion = "5.13.0.202109080827-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
.settings(
|
||||
)
|
||||
|
||||
sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.13.3"
|
||||
scalaVersion := "2.13.7"
|
||||
|
||||
scalafmtOnCompile := true
|
||||
|
||||
@@ -33,64 +31,80 @@ resolvers ++= Seq(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.6.10",
|
||||
"commons-io" % "commons-io" % "2.8.0",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||
"org.json4s" %% "json4s-jackson" % "4.0.3" cross CrossVersion.for3Use2_13,
|
||||
"commons-io" % "commons-io" % "2.11.0",
|
||||
"io.github.gitbucket" % "solidbase" % "1.0.3",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.16",
|
||||
"org.apache.commons" % "commons-compress" % "1.20",
|
||||
"org.apache.commons" % "commons-compress" % "1.21",
|
||||
"org.apache.commons" % "commons-email" % "1.5",
|
||||
"commons-net" % "commons-net" % "3.7",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.12",
|
||||
"commons-net" % "commons-net" % "3.8.0",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
|
||||
"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.24.1",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12",
|
||||
"org.apache.tika" % "tika-core" % "2.1.0",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13,
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.199",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.0",
|
||||
"org.postgresql" % "postgresql" % "42.2.6",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"com.zaxxer" % "HikariCP" % "3.4.5",
|
||||
"com.typesafe" % "config" % "1.4.0",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.4",
|
||||
"org.postgresql" % "postgresql" % "42.3.1",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.6",
|
||||
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
|
||||
"com.typesafe" % "config" % "1.4.1",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
||||
"org.cache2k" % "cache2k-all" % "1.2.4.Final",
|
||||
"net.coobird" % "thumbnailator" % "0.4.12",
|
||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.11",
|
||||
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
|
||||
"net.coobird" % "thumbnailator" % "0.4.14",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "8.29.1",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "9.19",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.13" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "3.3.3" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.37.0" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.14.3" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.14.3" % "test",
|
||||
"junit" % "junit" % "4.13.2" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
|
||||
"org.mockito" % "mockito-core" % "4.0.0" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.39.11" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.16.2" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.16.2" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "0.0.3",
|
||||
"org.kohsuke" % "github-api" % "1.116" % "test"
|
||||
"org.ec4j.core" % "ec4j-core" % "0.3.0",
|
||||
"org.kohsuke" % "github-api" % "1.135" % "test"
|
||||
)
|
||||
|
||||
libraryDependencies ~= {
|
||||
_.map {
|
||||
case x if x.name == "twirl-api" =>
|
||||
x cross CrossVersion.for3Use2_13
|
||||
case x =>
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
// Compiler settings
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method", "-feature")
|
||||
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
scalacOptions := Seq(
|
||||
"-deprecation",
|
||||
"-language:postfixOps",
|
||||
"-opt:l:method",
|
||||
"-feature",
|
||||
"-Wunused:imports",
|
||||
"-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
|
||||
)
|
||||
compile / javacOptions ++= Seq("-target", "8", "-source", "8")
|
||||
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
|
||||
// Test settings
|
||||
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
||||
testOptions in Test += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
|
||||
fork in Test := true
|
||||
Test / javaOptions += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
||||
Test / testOptions += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
|
||||
Test / fork := true
|
||||
|
||||
// Packaging options
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
|
||||
// Assembly settings
|
||||
test in assembly := {}
|
||||
assemblyMergeStrategy in assembly := {
|
||||
assembly / test := {}
|
||||
assembly / assemblyMergeStrategy := {
|
||||
case PathList("META-INF", xs @ _*) =>
|
||||
(xs map { _.toLowerCase }) match {
|
||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||
@@ -122,9 +136,9 @@ libraryDependencies ++= Seq(
|
||||
)
|
||||
|
||||
// Run package task before test to generate target/webapp for integration test
|
||||
test in Test := {
|
||||
Test / test := {
|
||||
_root_.sbt.Keys.`package`.value
|
||||
(test in Test).value
|
||||
(Test / test).value
|
||||
}
|
||||
|
||||
val executableKey = TaskKey[File]("executable")
|
||||
@@ -155,7 +169,7 @@ executableKey := {
|
||||
IO unzip (warFile, temp)
|
||||
|
||||
// include launcher classes
|
||||
val classDir = (Keys.classDirectory in Compile).value
|
||||
val classDir = (Compile / Keys.classDirectory).value
|
||||
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */ )
|
||||
launchClasses foreach { name =>
|
||||
IO copyFile (classDir / name, temp / name)
|
||||
@@ -186,7 +200,7 @@ executableKey := {
|
||||
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||
val outputFile = workDir / warName
|
||||
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest)
|
||||
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest, None)
|
||||
|
||||
// generate checksums
|
||||
Seq(
|
||||
@@ -258,12 +272,7 @@ pomExtra := (
|
||||
</developers>
|
||||
)
|
||||
|
||||
licenseOverrides := {
|
||||
case DepModuleInfo("com.github.bkromhout", "java-diff-utils", _) =>
|
||||
LicenseInfo(LicenseCategory.Apache, "Apache-2.0", "http://www.apache.org/licenses/LICENSE-2.0")
|
||||
}
|
||||
|
||||
testOptions in Test ++= {
|
||||
Test / testOptions ++= {
|
||||
if (scala.util.Properties.isWin) {
|
||||
Seq(
|
||||
Tests.Exclude(
|
||||
@@ -276,3 +285,8 @@ testOptions in Test ++= {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
Jetty / javaOptions ++= Seq(
|
||||
"-Xdebug",
|
||||
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ Authentication in Controller
|
||||
========
|
||||
GitBucket provides many [authenticators](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/util/Authenticator.scala) to access controlling in the controller.
|
||||
|
||||
For example, in the case of `RepositoryViwerController`,
|
||||
For example, in the case of `RepositoryViewerController`,
|
||||
it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
|
||||
|
||||
```scala
|
||||
@@ -19,13 +19,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
...
|
||||
```
|
||||
|
||||
Authenticators provides a method to add guard to actions in the controller:
|
||||
Authenticators provide a method to add guard to actions in the controller:
|
||||
|
||||
- `ReadableUsersAuthenticator` provides `readableUsersOnly` method
|
||||
- `ReferrerAuthenticator` provides `referrersOnly` method
|
||||
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
|
||||
|
||||
These methods are available in each actions as below:
|
||||
These methods are available in each action as below:
|
||||
|
||||
```scala
|
||||
// Allows only the repository owner (or manager for group repository) and administrators.
|
||||
@@ -38,7 +38,7 @@ get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||
...
|
||||
})
|
||||
|
||||
// Allows only signed in users which can access the repository.
|
||||
// Allows only signed-in users which can access the repository.
|
||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
...
|
||||
})
|
||||
@@ -50,11 +50,11 @@ Currently, GitBucket provides below authenticators:
|
||||
|--------------------------|-----------------|--------------------------------------------------------------------------------------|
|
||||
|OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|
||||
|OwnerAuthenticator |ownerOnly |Allows only the repository owner and administrators. |
|
||||
|UsersAuthenticator |usersOnly |Allows only signed in users. |
|
||||
|UsersAuthenticator |usersOnly |Allows only signed-in users. |
|
||||
|AdminAuthenticator |adminOnly |Allows only administrators. |
|
||||
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|
||||
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) and administrators.|
|
||||
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed in users which can access the repository. |
|
||||
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed-in users which can access the repository. |
|
||||
|GroupManagerAuthenticator |managersOnly |Allows only the group managers. |
|
||||
|
||||
Of course, if you make a new plugin, you can define a your own authenticator according to requirement in your plugin.
|
||||
Of course, if you make a new plugin, you can implement your own authenticator according to requirement in your plugin.
|
||||
|
||||
@@ -2,7 +2,7 @@ Automatic Schema Updating
|
||||
========
|
||||
GitBucket updates database schema automatically using [Solidbase](https://github.com/gitbucket/solidbase) in the first run after the upgrading.
|
||||
|
||||
To release a new version of GitBucket, add the version definition to the [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
||||
To release a new version of GitBucket, add the version definition to [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
||||
|
||||
```scala
|
||||
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
@@ -17,7 +17,7 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
)
|
||||
```
|
||||
|
||||
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filenane defined in `GitBucketCoreModule`.
|
||||
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filename defined in `GitBucketCoreModule`.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -31,9 +31,9 @@ Next, add a XML file which updates database schema into [/src/main/resources/upd
|
||||
</changeSet>
|
||||
```
|
||||
|
||||
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version differs from the actual version, it executes differences between the stored version and the actual version.
|
||||
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version different from the actual version, it executes differences between the stored version and the actual version.
|
||||
|
||||
We can add the SQL file instead of the XML file using `SqlMigration`. It try to load a SQL file from classpath as following order:
|
||||
We can add the SQL file instead of the XML file using `SqlMigration`. It tries to load a SQL file from classpath in the following order:
|
||||
|
||||
1. Specified path (if specified)
|
||||
2. `${moduleId}_${version}_${database}.sql`
|
||||
@@ -51,4 +51,4 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
)
|
||||
```
|
||||
|
||||
See more details [README of Solidbase](https://github.com/gitbucket/solidbase).
|
||||
See more details at [README of Solidbase](https://github.com/gitbucket/solidbase).
|
||||
|
||||
@@ -18,12 +18,12 @@ $ sbt ~jetty:start
|
||||
|
||||
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
|
||||
|
||||
Source code modifications are detected and a reloaded happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
|
||||
Source code modifications are detected and a reloading happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
|
||||
|
||||
Build war file
|
||||
--------
|
||||
|
||||
To build war file, run the following command:
|
||||
To build a war file, run the following command:
|
||||
|
||||
```shell
|
||||
$ sbt package
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.4.6
|
||||
sbt.version=1.5.5
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.2")
|
||||
|
||||
addDependencyTreePlugin
|
||||
|
||||
@@ -1,51 +1,100 @@
|
||||
import org.eclipse.jetty.server.ConnectionFactory;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
|
||||
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
||||
import org.eclipse.jetty.server.session.FileSessionDataStore;
|
||||
import org.eclipse.jetty.server.session.SessionCache;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.function.Function.identity;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class JettyLauncher {
|
||||
|
||||
private interface Defaults {
|
||||
|
||||
String CONNECTORS = "http";
|
||||
String HOST = "0.0.0.0";
|
||||
|
||||
int HTTP_PORT = 8080;
|
||||
int HTTPS_PORT = 8443;
|
||||
|
||||
boolean REDIRECT_HTTPS = false;
|
||||
}
|
||||
|
||||
private interface Connectors {
|
||||
|
||||
String HTTP = "http";
|
||||
String HTTPS = "https";
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
|
||||
String host = null;
|
||||
String port = null;
|
||||
InetSocketAddress address = null;
|
||||
String contextPath = "/";
|
||||
String tmpDirPath="";
|
||||
boolean forceHttps = false;
|
||||
String connectors = getEnvironmentVariable("gitbucket.connectors");
|
||||
String host = getEnvironmentVariable("gitbucket.host");
|
||||
String port = getEnvironmentVariable("gitbucket.port");
|
||||
String securePort = getEnvironmentVariable("gitbucket.securePort");
|
||||
String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath");
|
||||
String keyStorePassword = getEnvironmentVariable("gitbucket.keyStorePassword");
|
||||
String keyManagerPassword = getEnvironmentVariable("gitbucket.keyManagerPassword");
|
||||
String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps");
|
||||
String contextPath = getEnvironmentVariable("gitbucket.prefix");
|
||||
String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
||||
boolean saveSessions = false;
|
||||
|
||||
host = getEnvironmentVariable("gitbucket.host");
|
||||
port = getEnvironmentVariable("gitbucket.port");
|
||||
contextPath = getEnvironmentVariable("gitbucket.prefix");
|
||||
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
||||
|
||||
for(String arg: args) {
|
||||
if(arg.equals("--save_sessions")) {
|
||||
saveSessions = true;
|
||||
}
|
||||
if(arg.startsWith("--") && arg.contains("=")) {
|
||||
String[] dim = arg.split("=");
|
||||
if(dim.length >= 2) {
|
||||
String[] dim = arg.split("=", 2);
|
||||
if(dim.length == 2) {
|
||||
switch (dim[0]) {
|
||||
case "--connectors":
|
||||
connectors = dim[1];
|
||||
break;
|
||||
case "--host":
|
||||
host = dim[1];
|
||||
break;
|
||||
case "--port":
|
||||
port = dim[1];
|
||||
break;
|
||||
case "--secure_port":
|
||||
securePort = dim[1];
|
||||
break;
|
||||
case "--key_store_path":
|
||||
keyStorePath = dim[1];
|
||||
break;
|
||||
case "--key_store_password":
|
||||
keyStorePassword = dim[1];
|
||||
break;
|
||||
case "--key_manager_password":
|
||||
keyManagerPassword = dim[1];
|
||||
break;
|
||||
case "--redirect_https":
|
||||
redirectHttps = dim[1];
|
||||
break;
|
||||
case "--prefix":
|
||||
contextPath = dim[1];
|
||||
break;
|
||||
@@ -67,38 +116,69 @@ public class JettyLauncher {
|
||||
contextPath = "/" + contextPath;
|
||||
}
|
||||
|
||||
if(host != null) {
|
||||
address = new InetSocketAddress(host, getPort(port));
|
||||
} else {
|
||||
address = new InetSocketAddress(getPort(port));
|
||||
final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName();
|
||||
|
||||
final Server server = new Server();
|
||||
|
||||
final Set<String> connectorsSet = Stream.of(fallback(connectors, Defaults.CONNECTORS)
|
||||
.toLowerCase().split(",")).map(String::trim).collect(toSet());
|
||||
|
||||
final List<ServerConnector> connectorInstances = new ArrayList<>();
|
||||
|
||||
final HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.setSendServerVersion(false);
|
||||
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||
httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
|
||||
}
|
||||
|
||||
Server server = new Server(address);
|
||||
if (connectorsSet.contains(Connectors.HTTP)) {
|
||||
final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
|
||||
connector.setHost(hostName);
|
||||
connector.setPort(fallback(port, Defaults.HTTP_PORT, Integer::parseInt));
|
||||
|
||||
// SelectChannelConnector connector = new SelectChannelConnector();
|
||||
// if(host != null) {
|
||||
// connector.setHost(host);
|
||||
// }
|
||||
// connector.setMaxIdleTime(1000 * 60 * 60);
|
||||
// connector.setSoLingerTime(-1);
|
||||
// connector.setPort(port);
|
||||
// server.addConnector(connector);
|
||||
|
||||
// Disabling Server header
|
||||
for (Connector connector : server.getConnectors()) {
|
||||
for (ConnectionFactory factory : connector.getConnectionFactories()) {
|
||||
if (factory instanceof HttpConnectionFactory) {
|
||||
((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false);
|
||||
}
|
||||
}
|
||||
connectorInstances.add(connector);
|
||||
}
|
||||
|
||||
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
||||
|
||||
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
|
||||
"You must specify a path to an SSL keystore via the --key_store_path command line argument" +
|
||||
" or GITBUCKET_KEYSTOREPATH environment variable."));
|
||||
|
||||
sslContextFactory.setKeyStorePassword(requireNonNull(keyStorePassword,
|
||||
"You must specify a an SSL keystore password via the --key_store_password argument" +
|
||||
" or GITBUCKET_KEYSTOREPASSWORD environment variable."));
|
||||
|
||||
sslContextFactory.setKeyManagerPassword(requireNonNull(keyManagerPassword,
|
||||
"You must specify a key manager password via the --key_manager_password' argument" +
|
||||
" or GITBUCKET_KEYMANAGERPASSWORD environment variable."));
|
||||
|
||||
final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
|
||||
httpsConfig.addCustomizer(new SecureRequestCustomizer());
|
||||
|
||||
final ServerConnector connector = new ServerConnector(server,
|
||||
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
|
||||
new HttpConnectionFactory(httpsConfig));
|
||||
|
||||
connector.setHost(hostName);
|
||||
connector.setPort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
|
||||
|
||||
connectorInstances.add(connector);
|
||||
}
|
||||
|
||||
require(!connectorInstances.isEmpty(),
|
||||
"No server connectors could be configured, please check your --connectors command line argument" +
|
||||
" or GITBUCKET_CONNECTORS environment variable.");
|
||||
|
||||
server.setConnectors(connectorInstances.toArray(new ServerConnector[0]));
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
|
||||
if(saveSessions) {
|
||||
File sessDir = new File(getGitBucketHome(), "sessions");
|
||||
if(!sessDir.exists()){
|
||||
sessDir.mkdirs();
|
||||
mkdir(sessDir);
|
||||
}
|
||||
SessionHandler sessions = context.getSessionHandler();
|
||||
SessionCache cache = new DefaultSessionCache(sessions);
|
||||
@@ -112,7 +192,7 @@ public class JettyLauncher {
|
||||
if(tmpDirPath == null || tmpDirPath.equals("")){
|
||||
tmpDir = new File(getGitBucketHome(), "tmp");
|
||||
if(!tmpDir.exists()){
|
||||
tmpDir.mkdirs();
|
||||
mkdir(tmpDir);
|
||||
}
|
||||
} else {
|
||||
tmpDir = new File(tmpDirPath);
|
||||
@@ -136,13 +216,16 @@ public class JettyLauncher {
|
||||
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
||||
context.setServer(server);
|
||||
context.setWar(location.toExternalForm());
|
||||
if (forceHttps) {
|
||||
context.setInitParameter("org.scalatra.ForceHttps", "true");
|
||||
|
||||
final HandlerList handlers = new HandlerList();
|
||||
|
||||
if (fallback(redirectHttps, Defaults.REDIRECT_HTTPS, Boolean::parseBoolean)) {
|
||||
handlers.addHandler(new SecuredRedirectHandler());
|
||||
}
|
||||
|
||||
Handler handler = addStatisticsHandler(context);
|
||||
handlers.addHandler(addStatisticsHandler(context));
|
||||
|
||||
server.setHandler(handler);
|
||||
server.setHandler(handlers);
|
||||
server.setStopAtShutdown(true);
|
||||
server.setStopTimeout(7_000);
|
||||
server.start();
|
||||
@@ -170,11 +253,28 @@ public class JettyLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
private static int getPort(String port){
|
||||
if(port == null) {
|
||||
return 8080;
|
||||
} else {
|
||||
return Integer.parseInt(port);
|
||||
private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) {
|
||||
return value == null ? defaultValue : converter.apply(value);
|
||||
}
|
||||
|
||||
private static <T> T fallback(T value, T defaultValue) {
|
||||
return fallback(value, defaultValue, identity());
|
||||
}
|
||||
|
||||
private static void require(boolean condition, String message) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T requireNonNull(T value, String message) {
|
||||
require(value != null, message);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void mkdir(File dir) {
|
||||
if (!dir.mkdirs()) {
|
||||
throw new RuntimeException("Unable to create directory: " + dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,15 +20,15 @@ public class PatchUtil {
|
||||
public static String apply(String source, String patch, FileHeader fh)
|
||||
throws IOException, PatchApplyException {
|
||||
RawText rt = new RawText(source.getBytes("UTF-8"));
|
||||
List<String> oldLines = new ArrayList<String>(rt.size());
|
||||
List<String> oldLines = new ArrayList<>(rt.size());
|
||||
for (int i = 0; i < rt.size(); i++)
|
||||
oldLines.add(rt.getString(i));
|
||||
List<String> newLines = new ArrayList<String>(oldLines);
|
||||
List<String> newLines = new ArrayList<>(oldLines);
|
||||
for (HunkHeader hh : fh.getHunks()) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
|
||||
RawText hrt = new RawText(out.toByteArray());
|
||||
List<String> hunkLines = new ArrayList<String>(hrt.size());
|
||||
List<String> hunkLines = new ArrayList<>(hrt.size());
|
||||
for (int i = 0; i < hrt.size(); i++)
|
||||
hunkLines.add(hrt.getString(i));
|
||||
int pos = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.10.0
|
||||
gist:4.20.0
|
||||
gist:4.21.0
|
||||
emoji:4.6.0
|
||||
pages:1.9.0
|
||||
pages:1.10.0
|
||||
|
||||
6
src/main/resources/update/gitbucket-core_4.36.xml
Normal file
6
src/main/resources/update/gitbucket-core_4.36.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<addColumn tableName="REPOSITORY">
|
||||
<column name="SAFE_MODE" type="boolean" nullable="false" defaultValue="true"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
@@ -2,7 +2,6 @@ import java.util.EnumSet
|
||||
import javax.servlet._
|
||||
|
||||
import gitbucket.core.controller.{ReleaseController, _}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.servlet._
|
||||
import gitbucket.core.util.Directory
|
||||
|
||||
@@ -3,7 +3,6 @@ package gitbucket.core
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.sql.Connection
|
||||
import java.util
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
@@ -12,7 +11,7 @@ import gitbucket.core.util.JDBCUtil
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
|
||||
import io.github.gitbucket.solidbase.model.{Module, Version}
|
||||
import org.json4s.NoTypeHints
|
||||
import org.json4s.{Formats, NoTypeHints}
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.jackson.Serialization.write
|
||||
|
||||
@@ -84,8 +83,8 @@ object GitBucketCoreModule
|
||||
new Version(
|
||||
"4.34.0",
|
||||
new Migration() {
|
||||
override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = {
|
||||
implicit val formats = Serialization.formats(NoTypeHints)
|
||||
override def migrate(moduleId: String, version: String, context: java.util.Map[String, AnyRef]): Unit = {
|
||||
implicit val formats: Formats = Serialization.formats(NoTypeHints)
|
||||
import JDBCUtil._
|
||||
|
||||
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
|
||||
@@ -117,4 +116,8 @@ object GitBucketCoreModule
|
||||
new Version("4.35.0", new LiquibaseMigration("update/gitbucket-core_4.35.xml")),
|
||||
new Version("4.35.1"),
|
||||
new Version("4.35.2"),
|
||||
new Version("4.35.3"),
|
||||
new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")),
|
||||
new Version("4.36.1"),
|
||||
new Version("4.36.2")
|
||||
)
|
||||
|
||||
@@ -66,7 +66,7 @@ object ApiBranchProtection {
|
||||
}
|
||||
}
|
||||
|
||||
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](
|
||||
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] = new CustomSerializer[EnforcementLevel](
|
||||
format =>
|
||||
(
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ object ApiContents {
|
||||
"file",
|
||||
fileInfo.name,
|
||||
fileInfo.path,
|
||||
fileInfo.commitId,
|
||||
fileInfo.id.getName,
|
||||
Some(Base64.getEncoder.encodeToString(arr)),
|
||||
Some("base64")
|
||||
)(repositoryName)
|
||||
|
||||
@@ -17,7 +17,8 @@ case class ApiIssue(
|
||||
state: String,
|
||||
created_at: Date,
|
||||
updated_at: Date,
|
||||
body: String
|
||||
body: String,
|
||||
milestone: Option[ApiMilestone]
|
||||
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
|
||||
val id = 0 // dummy id
|
||||
val assignees = List(assignee).flatten
|
||||
@@ -43,7 +44,8 @@ object ApiIssue {
|
||||
repositoryName: RepositoryName,
|
||||
user: ApiUser,
|
||||
assignee: Option[ApiUser],
|
||||
labels: List[ApiLabel]
|
||||
labels: List[ApiLabel],
|
||||
milestone: Option[ApiMilestone]
|
||||
): ApiIssue =
|
||||
ApiIssue(
|
||||
number = issue.issueId,
|
||||
@@ -51,6 +53,7 @@ object ApiIssue {
|
||||
user = user,
|
||||
assignee = assignee,
|
||||
labels = labels,
|
||||
milestone = milestone,
|
||||
state = if (issue.closed) { "closed" } else { "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
created_at = issue.registeredDate,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.plugin.{PluginRegistry, PluginInfo}
|
||||
import gitbucket.core.plugin.PluginInfo
|
||||
|
||||
case class ApiPlugin(
|
||||
id: String,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.Profile.{RepositoryWebHookEvents, RepositoryWebHooks}
|
||||
import gitbucket.core.model.{RepositoryWebHook, WebHook}
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/rest/reference/repos#webhooks
|
||||
|
||||
@@ -86,7 +86,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val newForm = mapping(
|
||||
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -98,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
)(AccountNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -434,7 +434,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
getAccountByUserName(userName).foreach { x =>
|
||||
val (tokenId, token) = generateAccessToken(userName, form.note)
|
||||
flash.update("generatedToken", (tokenId, token))
|
||||
}
|
||||
@@ -629,7 +629,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
|
||||
get("/groups/new")(usersOnly {
|
||||
html.creategroup(List(GroupMember("", context.loginAccount.get.userName, true)))
|
||||
context.withLoginAccount { loginAccount =>
|
||||
html.creategroup(List(GroupMember("", loginAccount.userName, true)))
|
||||
}
|
||||
})
|
||||
|
||||
post("/groups/new", newGroupForm)(usersOnly { form =>
|
||||
@@ -650,22 +652,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/:groupName/_editgroup")(managersOnly {
|
||||
defining(params("groupName")) { groupName =>
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
val groupName = params("groupName")
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/:groupName/_deletegroup")(managersOnly {
|
||||
defining(params("groupName")) {
|
||||
groupName =>
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(groupName, Nil)
|
||||
// Disable group
|
||||
getAccountByUserName(groupName, false).foreach { account =>
|
||||
updateGroup(groupName, account.description, account.url, true)
|
||||
}
|
||||
val groupName = params("groupName")
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(groupName, Nil)
|
||||
// Disable group
|
||||
getAccountByUserName(groupName, false).foreach { account =>
|
||||
updateGroup(groupName, account.description, account.url, true)
|
||||
}
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
||||
// deleteRepository(groupName, repositoryName)
|
||||
@@ -673,28 +673,25 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||
// }
|
||||
}
|
||||
redirect("/")
|
||||
})
|
||||
|
||||
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
|
||||
defining(
|
||||
params("groupName"),
|
||||
form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
val groupName = params("groupName")
|
||||
val members = form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
.toList
|
||||
) {
|
||||
case (groupName, members) =>
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
updateGroup(groupName, form.description, form.url, false)
|
||||
}
|
||||
.toList
|
||||
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
updateGroup(groupName, form.description, form.url, false)
|
||||
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
// // Update COLLABORATOR for group repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// removeCollaborators(form.groupName, repositoryName)
|
||||
@@ -703,87 +700,104 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// }
|
||||
// }
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
|
||||
flash.update("info", "Account information has been updated.")
|
||||
redirect(s"/${groupName}/_editgroup")
|
||||
flash.update("info", "Account information has been updated.")
|
||||
redirect(s"/${groupName}/_editgroup")
|
||||
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
/**
|
||||
* Show the new repository form.
|
||||
*/
|
||||
get("/new")(usersOnly {
|
||||
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
|
||||
context.withLoginAccount { loginAccount =>
|
||||
html.newrepo(getGroupsByUserName(loginAccount.userName), context.settings.isCreateRepoOptionPublic)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Create new repository.
|
||||
*/
|
||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||
if (context.settings.repositoryOperation.create || context.loginAccount.get.isAdmin) {
|
||||
LockUtil.lock(s"${form.owner}/${form.name}") {
|
||||
if (getRepository(form.owner, form.name).isEmpty) {
|
||||
createRepository(
|
||||
context.loginAccount.get,
|
||||
form.owner,
|
||||
form.name,
|
||||
form.description,
|
||||
form.isPrivate,
|
||||
form.initOption,
|
||||
form.sourceUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
// redirect to the repository
|
||||
redirect(s"/${form.owner}/${form.name}")
|
||||
} else Forbidden()
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (context.settings.repositoryOperation.create || loginAccount.isAdmin) {
|
||||
LockUtil.lock(s"${form.owner}/${form.name}") {
|
||||
if (getRepository(form.owner, form.name).isDefined) {
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${form.owner}/${form.name}")
|
||||
} else if (!canCreateRepository(form.owner, loginAccount)) {
|
||||
// Permission error
|
||||
Forbidden()
|
||||
} else {
|
||||
// create repository asynchronously
|
||||
createRepository(
|
||||
loginAccount,
|
||||
form.owner,
|
||||
form.name,
|
||||
form.description,
|
||||
form.isPrivate,
|
||||
form.initOption,
|
||||
form.sourceUrl
|
||||
)
|
||||
// redirect to the repository
|
||||
redirect(s"/${form.owner}/${form.name}")
|
||||
}
|
||||
}
|
||||
} else Forbidden()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||
val loginAccount = context.loginAccount.get
|
||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||
val loginUserName = loginAccount.userName
|
||||
val groups = getGroupsByUserName(loginUserName)
|
||||
groups match {
|
||||
case _: List[String] =>
|
||||
val managerPermissions = groups.map { group =>
|
||||
val members = getGroupMembers(group)
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||
val loginUserName = loginAccount.userName
|
||||
val groups = getGroupsByUserName(loginUserName)
|
||||
groups match {
|
||||
case _: List[String] =>
|
||||
val managerPermissions = groups.map { group =>
|
||||
val members = getGroupMembers(group)
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
helper.html.forkrepository(
|
||||
repository,
|
||||
(groups zip managerPermissions).sortBy(_._1)
|
||||
)
|
||||
case _ => redirect(s"/${loginUserName}")
|
||||
}
|
||||
helper.html.forkrepository(
|
||||
repository,
|
||||
(groups zip managerPermissions).sortBy(_._1)
|
||||
)
|
||||
case _ => redirect(s"/${loginUserName}")
|
||||
}
|
||||
} else BadRequest()
|
||||
} else BadRequest()
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
||||
val loginAccount = context.loginAccount.get
|
||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||
val loginUserName = loginAccount.userName
|
||||
val accountName = form.accountName
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||
val loginUserName = loginAccount.userName
|
||||
val accountName = form.accountName
|
||||
|
||||
if (getRepository(accountName, repository.name).isDefined ||
|
||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) {
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
} else {
|
||||
// fork repository asynchronously
|
||||
forkRepository(accountName, repository, loginUserName)
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
} else Forbidden()
|
||||
if (getRepository(accountName, repository.name).isDefined) {
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
} else if (!canCreateRepository(accountName, loginAccount)) {
|
||||
// Permission error
|
||||
Forbidden()
|
||||
} else {
|
||||
// fork repository asynchronously
|
||||
forkRepository(accountName, repository, loginUserName)
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
} else Forbidden()
|
||||
}
|
||||
})
|
||||
|
||||
private def existsAccount: Constraint = new Constraint() {
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.eclipse.jgit.revwalk.RevCommit
|
||||
import org.eclipse.jgit.treewalk._
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.json4s.Formats
|
||||
|
||||
/**
|
||||
* Provides generic features for controller implementations.
|
||||
@@ -43,7 +44,7 @@ abstract class ControllerBase
|
||||
|
||||
private val logger = LoggerFactory.getLogger(getClass)
|
||||
|
||||
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
|
||||
before("/api/v3/*") {
|
||||
contentType = formats("json")
|
||||
@@ -157,11 +158,8 @@ abstract class ControllerBase
|
||||
org.scalatra.Unauthorized(
|
||||
redirect(
|
||||
"/signin?redirect=" + StringUtil.urlEncode(
|
||||
defining(request.getQueryString) { queryString =>
|
||||
request.getRequestURI.substring(request.getContextPath.length) + (if (queryString != null)
|
||||
"?" + queryString
|
||||
else "")
|
||||
}
|
||||
request.getRequestURI
|
||||
.substring(request.getContextPath.length) + Option(request.getQueryString).map("?" + _).getOrElse("")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -255,7 +253,7 @@ abstract class ControllerBase
|
||||
repository: RepositoryService.RepositoryInfo
|
||||
): Unit = {
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId) { loader =>
|
||||
contentType = FileUtil.getSafeMimeType(path)
|
||||
contentType = FileUtil.getSafeMimeType(path, repository.repository.options.safeMode)
|
||||
|
||||
if (loader.isLarge) {
|
||||
response.setContentLength(loader.getSize.toInt)
|
||||
@@ -302,20 +300,27 @@ case class Context(
|
||||
}
|
||||
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
|
||||
|
||||
def withLoginAccount(f: Account => Any): Any = {
|
||||
loginAccount match {
|
||||
case Some(loginAccount) => f(loginAccount)
|
||||
case None => Unauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object from cache.
|
||||
*
|
||||
* If object has not been cached with the specified key then retrieves by given action.
|
||||
* Cached object are available during a request.
|
||||
*/
|
||||
def cache[A](key: String)(action: => A): A =
|
||||
defining(Keys.Request.Cache(key)) { cacheKey =>
|
||||
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
||||
val newObject = action
|
||||
request.setAttribute(cacheKey, newObject)
|
||||
newObject
|
||||
}
|
||||
def cache[A](key: String)(action: => A): A = {
|
||||
val cacheKey = Keys.Request.Cache(key)
|
||||
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
||||
val newObject = action
|
||||
request.setAttribute(cacheKey, newObject)
|
||||
newObject
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.dashboard.html
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.{Keys, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -34,45 +35,63 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
with UsersAuthenticator =>
|
||||
|
||||
get("/dashboard/repos")(usersOnly {
|
||||
val repos = getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
html.repos(getGroupNames(context.loginAccount.get.userName), repos, repos)
|
||||
context.withLoginAccount { loginAccount =>
|
||||
val repos = getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
html.repos(getGroupNames(loginAccount.userName), repos, repos)
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/issues")(usersOnly {
|
||||
searchIssues("created_by")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchIssues(loginAccount, "created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/issues/assigned")(usersOnly {
|
||||
searchIssues("assigned")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchIssues(loginAccount, "assigned")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/issues/created_by")(usersOnly {
|
||||
searchIssues("created_by")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchIssues(loginAccount, "created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/issues/mentioned")(usersOnly {
|
||||
searchIssues("mentioned")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchIssues(loginAccount, "mentioned")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/pulls")(usersOnly {
|
||||
searchPullRequests("created_by")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchPullRequests(loginAccount, "created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/created_by")(usersOnly {
|
||||
searchPullRequests("created_by")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchPullRequests(loginAccount, "created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/assigned")(usersOnly {
|
||||
searchPullRequests("assigned")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchPullRequests(loginAccount, "assigned")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/mentioned")(usersOnly {
|
||||
searchPullRequests("mentioned")
|
||||
context.withLoginAccount { loginAccount =>
|
||||
searchPullRequests(loginAccount, "mentioned")
|
||||
}
|
||||
})
|
||||
|
||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||
@@ -85,10 +104,10 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def searchIssues(filter: String) = {
|
||||
private def searchIssues(loginAccount: Account, filter: String) = {
|
||||
import IssuesService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val userName = loginAccount.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
@@ -115,11 +134,11 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
)
|
||||
}
|
||||
|
||||
private def searchPullRequests(filter: String) = {
|
||||
private def searchPullRequests(loginAccount: Account, filter: String) = {
|
||||
import IssuesService._
|
||||
import PullRequestService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val userName = loginAccount.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
@@ -6,7 +6,6 @@ import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{AccountService, ReleaseService, RepositoryService}
|
||||
import gitbucket.core.servlet.Database
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -18,6 +17,7 @@ import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
|
||||
import scala.util.Using
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import slick.jdbc.JdbcBackend.Session
|
||||
|
||||
/**
|
||||
* Provides Ajax based file upload functionality.
|
||||
@@ -40,7 +40,7 @@ class FileUploadController
|
||||
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get())
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
},
|
||||
FileUtil.isImage
|
||||
FileUtil.isImage(_)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ class FileUploadController
|
||||
} getOrElse BadRequest()
|
||||
}
|
||||
|
||||
post("/release/:owner/:repository/:tag") {
|
||||
post("/release/:owner/:repository/*") {
|
||||
setMultipartConfigForLargeFile()
|
||||
session
|
||||
.get(Keys.Session.LoginAccount)
|
||||
@@ -139,7 +139,7 @@ class FileUploadController
|
||||
case _: Account =>
|
||||
val owner = params("owner")
|
||||
val repository = params("repository")
|
||||
val tag = params("tag")
|
||||
val tag = multiParams("splat").head
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(
|
||||
@@ -178,7 +178,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||
implicit val session = Database.getSession(request)
|
||||
implicit val session: Session = Database.getSession(request)
|
||||
getRepository(owner, repository) match {
|
||||
case Some(x) =>
|
||||
x.repository.options.wikiOption match {
|
||||
@@ -191,14 +191,13 @@ class FileUploadController
|
||||
}
|
||||
}
|
||||
|
||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) =
|
||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChecker: (String) => Boolean) =
|
||||
fileParams.get("file") match {
|
||||
case Some(file) if (mimeTypeChcker(file.name)) =>
|
||||
defining(FileUtil.generateFileId) { fileId =>
|
||||
f(file, fileId)
|
||||
contentType = "text/plain"
|
||||
Ok(fileId)
|
||||
}
|
||||
case Some(file) if mimeTypeChecker(file.name) =>
|
||||
val fileId = FileUtil.generateFileId
|
||||
f(file, fileId)
|
||||
contentType = "text/plain"
|
||||
Ok(fileId)
|
||||
case _ => BadRequest()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import gitbucket.core.helper.xml
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view.helpers._
|
||||
import org.scalatra.Ok
|
||||
@@ -237,50 +236,49 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
// TODO Move to RepositoryViewrController?
|
||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")) {
|
||||
case (query, target) =>
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if (i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
val query = params.getOrElse("q", "").trim
|
||||
val target = params.getOrElse("type", "code")
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if (i <= 0) 1 else i
|
||||
} catch {
|
||||
case _: NumberFormatException => 1
|
||||
}
|
||||
|
||||
target.toLowerCase match {
|
||||
case "issues" =>
|
||||
gitbucket.core.search.html.issues(
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
|
||||
false,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
target.toLowerCase match {
|
||||
case "issues" =>
|
||||
gitbucket.core.search.html.issues(
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
|
||||
false,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
|
||||
case "pulls" =>
|
||||
gitbucket.core.search.html.issues(
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
|
||||
true,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
case "pulls" =>
|
||||
gitbucket.core.search.html.issues(
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
|
||||
true,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
|
||||
case "wiki" =>
|
||||
gitbucket.core.search.html.wiki(
|
||||
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
case "wiki" =>
|
||||
gitbucket.core.search.html.wiki(
|
||||
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
|
||||
case _ =>
|
||||
gitbucket.core.search.html.code(
|
||||
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
gitbucket.core.search.html.code(
|
||||
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.html
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view
|
||||
@@ -95,212 +95,224 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
defining(repository.owner, repository.name, params("id")) {
|
||||
case (owner, name, issueId) =>
|
||||
getIssue(owner, name, issueId) map {
|
||||
issue =>
|
||||
if (issue.isPullRequest) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
html.issue(
|
||||
issue,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getLabels(owner, name),
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository),
|
||||
isIssueCommentManageable(repository),
|
||||
repository
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
val issueId = params("id")
|
||||
getIssue(repository.owner, repository.name, issueId) map {
|
||||
issue =>
|
||||
if (issue.isPullRequest) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
html.issue(
|
||||
issue,
|
||||
getComments(repository.owner, repository.name, issueId.toInt),
|
||||
getIssueLabels(repository.owner, repository.name, issueId.toInt),
|
||||
getAssignableUserNames(repository.owner, repository.name),
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository),
|
||||
isIssueCommentManageable(repository),
|
||||
repository
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
html.create(
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestones(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getDefaultPriority(owner, name),
|
||||
getLabels(owner, name),
|
||||
isIssueManageable(repository),
|
||||
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||
repository
|
||||
)
|
||||
}
|
||||
html.create(
|
||||
getAssignableUserNames(repository.owner, repository.name),
|
||||
getMilestones(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getDefaultPriority(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
isIssueManageable(repository),
|
||||
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||
repository
|
||||
)
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||
val issue = createIssue(
|
||||
repository,
|
||||
form.title,
|
||||
form.content,
|
||||
form.assignedUserName,
|
||||
form.milestoneId,
|
||||
form.priorityId,
|
||||
form.labelNames.toSeq.flatMap(_.split(",")),
|
||||
context.loginAccount.get
|
||||
)
|
||||
|
||||
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||
val issue = createIssue(
|
||||
repository,
|
||||
form.title,
|
||||
form.content,
|
||||
form.assignedUserName,
|
||||
form.milestoneId,
|
||||
form.priorityId,
|
||||
form.labelNames.toSeq.flatMap(_.split(",")),
|
||||
loginAccount
|
||||
)
|
||||
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map {
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
getIssue(repository.owner, repository.name, params("id")).map {
|
||||
issue =>
|
||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
||||
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
|
||||
if (issue.title != title) {
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
||||
updateIssue(repository.owner, repository.name, issue.issueId, title, issue.content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
||||
createReferComment(repository.owner, repository.name, issue.copy(title = title), title, loginAccount)
|
||||
createComment(
|
||||
owner,
|
||||
name,
|
||||
context.loginAccount.get.userName,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issue.issueId,
|
||||
issue.title + "\r\n" + title,
|
||||
"change_title"
|
||||
)
|
||||
}
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
getIssue(repository.owner, repository.name, params("id")).map { issue =>
|
||||
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
|
||||
// update issue
|
||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
||||
updateIssue(repository.owner, repository.name, issue.issueId, issue.title, content)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
|
||||
createReferComment(repository.owner, repository.name, issue, content.getOrElse(""), loginAccount)
|
||||
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt =
|
||||
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, Some(form.content), repository, actionOpt) map {
|
||||
case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt =
|
||||
params
|
||||
.get("action")
|
||||
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
|
||||
handleComment(issue, Some(form.content), repository, actionOpt) map {
|
||||
case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt =
|
||||
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
||||
handleComment(issue, form.content, repository, actionOpt) map {
|
||||
case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||
val actionOpt =
|
||||
params
|
||||
.get("action")
|
||||
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
|
||||
handleComment(issue, form.content, repository, actionOpt) map {
|
||||
case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if (isEditableContent(owner, name, comment.commentedUserName)) {
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
getComment(repository.owner, repository.name, params("id")).map { comment =>
|
||||
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
|
||||
updateComment(comment.issueId, comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/issue_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if (isDeletableComment(owner, name, comment.commentedUserName)) {
|
||||
Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId))
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
context.withLoginAccount { loginAccount =>
|
||||
getComment(repository.owner, repository.name, params("id")).map { comment =>
|
||||
if (isDeletableComment(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
|
||||
Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId))
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||
getIssue(repository.owner, repository.name, params("id")) map {
|
||||
x =>
|
||||
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName)) {
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"title" -> x.title,
|
||||
"content" -> Markdown.toHtml(
|
||||
markdown = x.content getOrElse "No description given.",
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
getIssue(repository.owner, repository.name, params("id")) map {
|
||||
x =>
|
||||
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"title" -> x.title,
|
||||
"content" -> Markdown.toHtml(
|
||||
markdown = x.content getOrElse "No description given.",
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
getComment(repository.owner, repository.name, params("id")) map {
|
||||
x =>
|
||||
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName)) {
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
getComment(repository.owner, repository.name, params("id")) map {
|
||||
x =>
|
||||
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository =>
|
||||
@@ -310,17 +322,15 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
|
||||
defining(params("id").toInt) { issueId =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
val issueId = params("id").toInt
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
|
||||
defining(params("id").toInt) { issueId =>
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
val issueId = params("id").toInt
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
|
||||
@@ -353,26 +363,27 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
|
||||
defining(params.get("value")) {
|
||||
action =>
|
||||
action match {
|
||||
case Some("open") =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("reopen"))
|
||||
}
|
||||
}
|
||||
case Some("close") =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("close"))
|
||||
}
|
||||
}
|
||||
case _ => BadRequest()
|
||||
val action = params.get("value")
|
||||
action match {
|
||||
case Some("open") =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("reopen"))
|
||||
}
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
case Some("close") =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
handleComment(issue, None, repository, Some("close"))
|
||||
}
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
case _ => BadRequest()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -390,29 +401,26 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
|
||||
defining(assignedUserName("value")) { value =>
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
val value = assignedUserName("value")
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
|
||||
defining(milestoneId("value")) { value =>
|
||||
executeBatch(repository) {
|
||||
updateMilestoneId(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
val value = milestoneId("value")
|
||||
executeBatch(repository) {
|
||||
updateMilestoneId(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
|
||||
defining(priorityId("value")) { value =>
|
||||
executeBatch(repository) {
|
||||
updatePriorityId(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
val value = priorityId("value")
|
||||
executeBatch(repository) {
|
||||
updatePriorityId(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -464,48 +472,51 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
// search issues
|
||||
val issues =
|
||||
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
// search issues
|
||||
val issues =
|
||||
searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.Issues,
|
||||
(page - 1) * IssueLimit,
|
||||
IssueLimit,
|
||||
repository.owner -> repository.name
|
||||
)
|
||||
|
||||
html.list(
|
||||
"issues",
|
||||
issues.map(issue => (issue, None)),
|
||||
page,
|
||||
getAssignableUserNames(owner, repoName),
|
||||
getMilestones(owner, repoName),
|
||||
getPriorities(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository)
|
||||
)
|
||||
}
|
||||
html.list(
|
||||
"issues",
|
||||
issues.map(issue => (issue, None)),
|
||||
page,
|
||||
getAssignableUserNames(repository.owner, repository.name),
|
||||
getMilestones(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, repository.owner -> repository.name),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, repository.owner -> repository.name),
|
||||
condition,
|
||||
repository,
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an issue or a comment is editable by a logged-in user.
|
||||
*/
|
||||
private def isEditableContent(owner: String, repository: String, author: String)(
|
||||
private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)(
|
||||
implicit context: Context
|
||||
): Boolean = {
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == loginAccount.userName
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an issue comment is deletable by a logged-in user.
|
||||
*/
|
||||
private def isDeletableComment(owner: String, repository: String, author: String)(
|
||||
private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)(
|
||||
implicit context: Context
|
||||
): Boolean = {
|
||||
hasOwnerRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
hasOwnerRole(owner, repository, context.loginAccount) || author == loginAccount.userName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ trait PreProcessControllerBase extends ControllerBase {
|
||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
|
||||
!context.currentPath.startsWith("/plugin-assets") &&
|
||||
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
|
||||
context.currentPath.startsWith(path)
|
||||
}) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service.PullRequestService._
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
@@ -58,7 +57,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
with PrioritiesService =>
|
||||
|
||||
val pullRequestForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(label("Content", optional(text()))),
|
||||
"targetUserName" -> trim(text(required, maxlength(100))),
|
||||
"targetBranch" -> trim(text(required, maxlength(100))),
|
||||
@@ -111,24 +110,29 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||
params("id").toIntOpt.flatMap {
|
||||
issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
val (commits, diffs) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
getRequestCompareInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdFrom,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdTo
|
||||
)
|
||||
|
||||
html.conversation(
|
||||
issue,
|
||||
pullreq,
|
||||
commits.flatten,
|
||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
||||
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
|
||||
diffs.size,
|
||||
getIssueLabels(owner, name, issueId),
|
||||
getAssignableUserNames(owner, name),
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
getPriorities(owner, name),
|
||||
getLabels(owner, name),
|
||||
getIssueLabels(repository.owner, repository.name, issueId),
|
||||
getAssignableUserNames(repository.owner, repository.name),
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
isEditable(repository),
|
||||
isManageable(repository),
|
||||
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
|
||||
@@ -162,12 +166,17 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
|
||||
params("id").toIntOpt.flatMap {
|
||||
issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
val (commits, diffs) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
getRequestCompareInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdFrom,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdTo
|
||||
)
|
||||
|
||||
val commitsWithStatus = commits.map { day =>
|
||||
day.map { commit =>
|
||||
@@ -179,7 +188,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
issue,
|
||||
pullreq,
|
||||
commitsWithStatus,
|
||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
||||
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
|
||||
diffs.size,
|
||||
isManageable(repository),
|
||||
repository
|
||||
@@ -191,19 +200,24 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
|
||||
params("id").toIntOpt.flatMap {
|
||||
issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
val (commits, diffs) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
getRequestCompareInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdFrom,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdTo
|
||||
)
|
||||
|
||||
html.files(
|
||||
issue,
|
||||
pullreq,
|
||||
diffs,
|
||||
commits.flatten,
|
||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
||||
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
|
||||
isManageable(repository),
|
||||
repository
|
||||
)
|
||||
@@ -214,39 +228,35 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
|
||||
params("id").toIntOpt.flatMap {
|
||||
issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
val conflictMessage = LockUtil.lock(s"${owner}/${name}") {
|
||||
checkConflict(owner, name, pullreq.branch, issueId)
|
||||
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
|
||||
}
|
||||
val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount)
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
|
||||
val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
|
||||
val mergeStatus = PullRequestService.MergeStatus(
|
||||
conflictMessage = conflictMessage,
|
||||
commitStatuses = getCommitStatuses(owner, name, pullreq.commitIdTo),
|
||||
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
|
||||
branchProtection = branchProtection,
|
||||
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
|
||||
needStatusCheck = context.loginAccount
|
||||
.map { u =>
|
||||
branchProtection.needStatusCheck(u.userName)
|
||||
}
|
||||
.getOrElse(true),
|
||||
branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some(
|
||||
pullreq.commitIdFrom
|
||||
),
|
||||
needStatusCheck = context.loginAccount.forall { u =>
|
||||
branchProtection.needStatusCheck(u.userName)
|
||||
},
|
||||
hasUpdatePermission = hasDeveloperRole(
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
context.loginAccount
|
||||
) &&
|
||||
context.loginAccount
|
||||
.map { u =>
|
||||
!getProtectedBranchInfo(
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
pullreq.requestBranch
|
||||
).needStatusCheck(u.userName)
|
||||
}
|
||||
.getOrElse(false),
|
||||
context.loginAccount.exists { u =>
|
||||
!getProtectedBranchInfo(
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
pullreq.requestBranch
|
||||
).needStatusCheck(u.userName)
|
||||
},
|
||||
hasMergePermission = hasMergePermission,
|
||||
commitIdTo = pullreq.commitIdTo
|
||||
)
|
||||
@@ -363,23 +373,22 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
|
||||
params("id").toIntOpt.flatMap { issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
mergePullRequest(
|
||||
repository,
|
||||
issueId,
|
||||
context.loginAccount.get,
|
||||
form.message,
|
||||
form.strategy,
|
||||
form.isDraft,
|
||||
context.settings
|
||||
) match {
|
||||
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
case Left(message) => Some(BadRequest(message))
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
context.withLoginAccount { loginAccount =>
|
||||
params("id").toIntOpt.flatMap { issueId =>
|
||||
mergePullRequest(
|
||||
repository,
|
||||
issueId,
|
||||
loginAccount,
|
||||
form.message,
|
||||
form.strategy,
|
||||
form.isDraft,
|
||||
context.settings
|
||||
) match {
|
||||
case Right(objectId) => redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
case Left(message) => Some(BadRequest(message))
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
||||
@@ -481,8 +490,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
(repository.userName, repository.repositoryName, repository.defaultBranch)
|
||||
},
|
||||
commits.flatten
|
||||
.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
||||
.flatten
|
||||
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
||||
.toList,
|
||||
originId,
|
||||
forkedId,
|
||||
@@ -549,15 +557,14 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
val manageable = isManageable(repository)
|
||||
val loginUserName = context.loginAccount.get.userName
|
||||
|
||||
val issueId = insertIssue(
|
||||
owner = repository.owner,
|
||||
repository = repository.name,
|
||||
loginUser = loginUserName,
|
||||
loginUser = loginAccount.userName,
|
||||
title = form.title,
|
||||
content = form.content,
|
||||
assignedUserName = if (manageable) form.assignedUserName else None,
|
||||
@@ -576,14 +583,14 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
commitIdFrom = form.commitIdFrom,
|
||||
commitIdTo = form.commitIdTo,
|
||||
isDraft = form.isDraft,
|
||||
loginAccount = context.loginAccount.get,
|
||||
loginAccount = loginAccount,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
// insert labels
|
||||
if (manageable) {
|
||||
form.labelNames.foreach { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
val labels = getLabels(repository.owner, repository.name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
|
||||
@@ -592,7 +599,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -639,44 +646,42 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
html.proposals(proposedBranches, targetRepository, repository)
|
||||
})
|
||||
|
||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
// search issues
|
||||
val issues = searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.PullRequests,
|
||||
(page - 1) * PullRequestLimit,
|
||||
PullRequestLimit,
|
||||
owner -> repoName
|
||||
)
|
||||
// commit status
|
||||
val status = issues.map { issue =>
|
||||
issue.commitId.flatMap { commitId =>
|
||||
getCommitStatusWithSummary(owner, repoName, commitId)
|
||||
}
|
||||
}
|
||||
|
||||
gitbucket.core.issues.html.list(
|
||||
"pulls",
|
||||
issues.zip(status),
|
||||
page,
|
||||
getAssignableUserNames(owner, repoName),
|
||||
getMilestones(owner, repoName),
|
||||
getPriorities(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
isEditable(repository),
|
||||
isManageable(repository)
|
||||
)
|
||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
|
||||
val page = IssueSearchCondition.page(request)
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
// search issues
|
||||
val issues = searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.PullRequests,
|
||||
(page - 1) * PullRequestLimit,
|
||||
PullRequestLimit,
|
||||
repository.owner -> repository.name
|
||||
)
|
||||
// commit status
|
||||
val status = issues.map { issue =>
|
||||
issue.commitId.flatMap { commitId =>
|
||||
getCommitStatusWithSummary(repository.owner, repository.name, commitId)
|
||||
}
|
||||
}
|
||||
|
||||
gitbucket.core.issues.html.list(
|
||||
"pulls",
|
||||
issues.zip(status),
|
||||
page,
|
||||
getAssignableUserNames(repository.owner, repository.name),
|
||||
getMilestones(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
|
||||
condition,
|
||||
repository,
|
||||
isEditable(repository),
|
||||
isManageable(repository)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can manage pull requests.
|
||||
*/
|
||||
|
||||
@@ -63,8 +63,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag")(referrersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
get("/:owner/:repository/releases/*")(referrersOnly { repository =>
|
||||
val tagName = multiParams("splat").head
|
||||
getRelease(repository.owner, repository.name, tagName)
|
||||
.map { release =>
|
||||
html.release(
|
||||
@@ -77,8 +77,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
get("/:owner/:repository/releases/*/assets/:fileId")(referrersOnly { repository =>
|
||||
val tagName = multiParams("splat").head
|
||||
val fileId = params("fileId")
|
||||
(for {
|
||||
_ <- repository.tags.find(_.name == tagName)
|
||||
@@ -93,8 +93,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
}).getOrElse(NotFound())
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
get("/:owner/:repository/releases/*/create")(writableUsersOnly { repository =>
|
||||
val tagName = multiParams("splat").head
|
||||
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
||||
|
||||
repository.tags
|
||||
@@ -105,33 +105,35 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
post("/:owner/:repository/releases/:tag/create", releaseForm)(writableUsersOnly { (form, repository) =>
|
||||
val tagName = params("tag")
|
||||
val loginAccount = context.loginAccount.get
|
||||
post("/:owner/:repository/releases/*/create", releaseForm)(writableUsersOnly { (form, repository) =>
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
val tagName = multiParams("splat").head
|
||||
|
||||
// Insert into RELEASE
|
||||
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
|
||||
// Insert into RELEASE
|
||||
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
|
||||
|
||||
// Insert into RELEASE_ASSET
|
||||
val files = params.toMap.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
// Insert into RELEASE_ASSET
|
||||
val files = params.toMap.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
}
|
||||
files.foreach {
|
||||
case (fileId, fileName) =>
|
||||
val size =
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||
).length
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||
recordActivity(releaseInfo)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||
}
|
||||
files.foreach {
|
||||
case (fileId, fileName) =>
|
||||
val size =
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||
).length
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||
recordActivity(releaseInfo)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
|
||||
@@ -150,8 +152,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
commitLog
|
||||
})
|
||||
|
||||
get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
get("/:owner/:repository/releases/*/edit")(writableUsersOnly { repository =>
|
||||
val tagName = multiParams("splat").head
|
||||
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
||||
|
||||
(for {
|
||||
@@ -168,52 +170,54 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
}).getOrElse(NotFound())
|
||||
})
|
||||
|
||||
post("/:owner/:repository/releases/:tag/edit", releaseForm)(writableUsersOnly {
|
||||
(form, repository) =>
|
||||
val tagName = params("tag")
|
||||
val loginAccount = context.loginAccount.get
|
||||
post("/:owner/:repository/releases/*/edit", releaseForm)(writableUsersOnly { (form, repository) =>
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
val tagName = multiParams("splat").head
|
||||
|
||||
getRelease(repository.owner, repository.name, tagName)
|
||||
.map { release =>
|
||||
// Update RELEASE
|
||||
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
|
||||
getRelease(repository.owner, repository.name, tagName)
|
||||
.map {
|
||||
release =>
|
||||
// Update RELEASE
|
||||
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
|
||||
|
||||
// Delete and Insert RELEASE_ASSET
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
|
||||
deleteReleaseAssets(repository.owner, repository.name, tagName)
|
||||
// Delete and Insert RELEASE_ASSET
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
|
||||
deleteReleaseAssets(repository.owner, repository.name, tagName)
|
||||
|
||||
val files = params.toMap.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
val files = params.toMap.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
}
|
||||
files.foreach {
|
||||
case (fileId, fileName) =>
|
||||
val size =
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||
).length
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
assets.foreach { asset =>
|
||||
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
|
||||
val file = new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
|
||||
)
|
||||
FileUtils.forceDelete(file)
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
|
||||
}
|
||||
files.foreach {
|
||||
case (fileId, fileName) =>
|
||||
val size =
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||
).length
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
assets.foreach { asset =>
|
||||
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
|
||||
val file = new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
|
||||
)
|
||||
FileUtils.forceDelete(file)
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
.getOrElse(NotFound())
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/releases/:tag/delete")(writableUsersOnly { repository =>
|
||||
val tagName = params("tag")
|
||||
post("/:owner/:repository/releases/*/delete")(writableUsersOnly { repository =>
|
||||
val tagName = multiParams("splat").head
|
||||
getRelease(repository.owner, repository.name, tagName).foreach { release =>
|
||||
FileUtils.deleteDirectory(
|
||||
new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(release.tag))
|
||||
@@ -224,7 +228,6 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
|
||||
|
||||
import gitbucket.core.service.ReleaseService._
|
||||
|
||||
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.time.{LocalDateTime, ZoneId, ZoneOffset}
|
||||
import java.time.{LocalDateTime, ZoneOffset}
|
||||
import java.util.Date
|
||||
|
||||
import gitbucket.core.settings.html
|
||||
@@ -57,7 +57,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
externalWikiUrl: Option[String],
|
||||
allowFork: Boolean,
|
||||
mergeOptions: Seq[String],
|
||||
defaultMergeOption: String
|
||||
defaultMergeOption: String,
|
||||
safeMode: Boolean
|
||||
)
|
||||
|
||||
val optionsForm = mapping(
|
||||
@@ -69,7 +70,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
"externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))),
|
||||
"allowFork" -> trim(label("Allow Forking", boolean())),
|
||||
"mergeOptions" -> mergeOptions,
|
||||
"defaultMergeOption" -> trim(label("Default merge strategy", text(required)))
|
||||
"defaultMergeOption" -> trim(label("Default merge strategy", text(required))),
|
||||
"safeMode" -> trim(label("XSS protection", boolean()))
|
||||
)(OptionsForm.apply).verifying { form =>
|
||||
if (!form.mergeOptions.contains(form.defaultMergeOption)) {
|
||||
Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.")
|
||||
@@ -150,7 +152,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
form.externalWikiUrl,
|
||||
form.allowFork,
|
||||
form.mergeOptions,
|
||||
form.defaultMergeOption
|
||||
form.defaultMergeOption,
|
||||
form.safeMode
|
||||
)
|
||||
flash.update("info", "Repository settings has been updated.")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
@@ -342,7 +345,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
.map(
|
||||
res =>
|
||||
Map(
|
||||
"status" -> res.getStatusLine(),
|
||||
"status" -> res.getStatusLine.getStatusCode,
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
)
|
||||
@@ -385,54 +388,62 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Rename repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
|
||||
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) {
|
||||
if (repository.name != form.repositoryName) {
|
||||
// Update database and move git repository
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
// Record activity log
|
||||
val renameInfo = RenameRepositoryInfo(
|
||||
repository.owner,
|
||||
form.repositoryName,
|
||||
context.loginAccount.get.userName,
|
||||
repository.name
|
||||
)
|
||||
recordActivity(renameInfo)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}")
|
||||
} else Forbidden()
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (context.settings.repositoryOperation.rename || loginAccount.isAdmin) {
|
||||
if (repository.name != form.repositoryName) {
|
||||
// Update database and move git repository
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
// Record activity log
|
||||
val renameInfo = RenameRepositoryInfo(
|
||||
repository.owner,
|
||||
form.repositoryName,
|
||||
loginAccount.userName,
|
||||
repository.name
|
||||
)
|
||||
recordActivity(renameInfo)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}")
|
||||
} else Forbidden()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Transfer repository ownership.
|
||||
*/
|
||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) {
|
||||
// Change repository owner
|
||||
if (repository.owner != form.newOwner) {
|
||||
// Update database and move git repository
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
// Record activity log
|
||||
val renameInfo = RenameRepositoryInfo(
|
||||
form.newOwner,
|
||||
repository.name,
|
||||
context.loginAccount.get.userName,
|
||||
repository.owner
|
||||
)
|
||||
recordActivity(renameInfo)
|
||||
}
|
||||
redirect(s"/${form.newOwner}/${repository.name}")
|
||||
} else Forbidden()
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (context.settings.repositoryOperation.transfer || loginAccount.isAdmin) {
|
||||
// Change repository owner
|
||||
if (repository.owner != form.newOwner) {
|
||||
// Update database and move git repository
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
// Record activity log
|
||||
val renameInfo = RenameRepositoryInfo(
|
||||
form.newOwner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
repository.owner
|
||||
)
|
||||
recordActivity(renameInfo)
|
||||
}
|
||||
redirect(s"/${form.newOwner}/${repository.name}")
|
||||
} else Forbidden()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete the repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||
if (context.settings.repositoryOperation.delete || context.loginAccount.get.isAdmin) {
|
||||
// Delete the repository and related files
|
||||
deleteRepository(repository.repository)
|
||||
redirect(s"/${repository.owner}")
|
||||
} else Forbidden()
|
||||
context.withLoginAccount { loginAccount =>
|
||||
if (context.settings.repositoryOperation.delete || loginAccount.isAdmin) {
|
||||
// Delete the repository and related files
|
||||
deleteRepository(repository.repository)
|
||||
redirect(s"/${repository.owner}")
|
||||
} else Forbidden()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@ import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.ssh.SshServer
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.mail.EmailException
|
||||
@@ -187,7 +186,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val newUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -201,7 +200,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val editUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -362,8 +361,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/admin/users")(adminOnly {
|
||||
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
|
||||
val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false)
|
||||
val includeRemoved = params.get("includeRemoved").exists(_.toBoolean)
|
||||
val includeGroups = params.get("includeGroups").exists(_.toBoolean)
|
||||
val users = getAllUsers(includeRemoved, includeGroups)
|
||||
val members = users.collect {
|
||||
case account if (account.isGroupAccount) =>
|
||||
@@ -463,31 +462,28 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||
defining(params("groupName")) { groupName =>
|
||||
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
}
|
||||
val groupName = params("groupName")
|
||||
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
})
|
||||
|
||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||
defining(
|
||||
params("groupName"),
|
||||
form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
val groupName = params("groupName")
|
||||
val members = form.members
|
||||
.split(",")
|
||||
.map {
|
||||
_.split(":") match {
|
||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||
}
|
||||
.toList
|
||||
) {
|
||||
case (groupName, members) =>
|
||||
getAccountByUserName(groupName, true).map {
|
||||
account =>
|
||||
updateGroup(groupName, form.description, form.url, form.isRemoved)
|
||||
}
|
||||
.toList
|
||||
|
||||
if (form.isRemoved) {
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, Nil)
|
||||
getAccountByUserName(groupName, true).map {
|
||||
account =>
|
||||
updateGroup(groupName, form.description, form.url, form.isRemoved)
|
||||
|
||||
if (form.isRemoved) {
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, Nil)
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// deleteRepository(groupName, repositoryName)
|
||||
@@ -495,9 +491,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||
// }
|
||||
} else {
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
} else {
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
// // Update COLLABORATOR for group repositories
|
||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
// removeCollaborators(form.groupName, repositoryName)
|
||||
@@ -505,13 +501,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
// addCollaborator(form.groupName, repositoryName, userName)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
redirect("/admin/users")
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
redirect("/admin/users")
|
||||
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
get("/admin/data")(adminOnly {
|
||||
@@ -559,12 +554,11 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
protected def disableByNotYourself(paramName: String): Constraint =
|
||||
new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
params.get(paramName).flatMap { userName =>
|
||||
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
||||
Some("You can't disable your account yourself")
|
||||
else
|
||||
None
|
||||
}
|
||||
for {
|
||||
userName <- params.get(paramName)
|
||||
loginAccount <- context.loginAccount
|
||||
if userName == loginAccount.userName && params.get("removed") == Some("true")
|
||||
} yield "You can't disable your account yourself"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,32 +136,38 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||
} else {
|
||||
flash.update("info", "This patch was not able to be reversed.")
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName))) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||
} else {
|
||||
flash.update("info", "This patch was not able to be reversed.")
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
|
||||
)
|
||||
}
|
||||
} else Unauthorized()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
|
||||
if (isEditable(repository)) {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (isEditable(repository)) {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
} else {
|
||||
flash.update("info", "This patch was not able to be reversed.")
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||
}
|
||||
} else Unauthorized()
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
} else {
|
||||
flash.update("info", "This patch was not able to be reversed.")
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||
}
|
||||
} else Unauthorized()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
|
||||
@@ -172,9 +178,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
|
||||
if (isEditable(repository)) {
|
||||
defining(context.loginAccount.get) {
|
||||
loginAccount =>
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (isEditable(repository)) {
|
||||
saveWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
@@ -201,8 +207,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
}
|
||||
} else Unauthorized()
|
||||
} else Unauthorized()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
|
||||
@@ -212,9 +218,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
|
||||
if (isEditable(repository)) {
|
||||
defining(context.loginAccount.get) {
|
||||
loginAccount =>
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (isEditable(repository)) {
|
||||
saveWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
@@ -242,27 +248,35 @@ trait WikiControllerBase extends ControllerBase {
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
}
|
||||
} else Unauthorized()
|
||||
} else Unauthorized()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
deleteWikiPage(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pageName,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
s"Destroyed ${pageName}"
|
||||
)
|
||||
val deleteWikiInfo = DeleteWikiInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
pageName
|
||||
)
|
||||
recordActivity(deleteWikiInfo)
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
defining(context.loginAccount.get) { loginAccount =>
|
||||
val deleteWikiInfo = DeleteWikiInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
pageName
|
||||
)
|
||||
recordActivity(deleteWikiInfo)
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
} else Unauthorized()
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
} else Unauthorized()
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||
|
||||
@@ -4,7 +4,7 @@ import gitbucket.core.controller.{Context, ControllerBase}
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||
import org.scalatra.{ActionResult, NoContent}
|
||||
import org.scalatra.ActionResult
|
||||
|
||||
trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||
self: AccountService
|
||||
|
||||
@@ -5,7 +5,7 @@ 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.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||
import gitbucket.core.util.Implicits._
|
||||
|
||||
trait ApiIssueControllerBase extends ControllerBase {
|
||||
@@ -47,7 +47,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
user = ApiUser(issueUser),
|
||||
assignee = assignedUser.map(ApiUser(_)),
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -69,7 +70,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
RepositoryName(repository),
|
||||
ApiUser(openedUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))),
|
||||
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
@@ -103,7 +105,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
ApiUser(loginAccount),
|
||||
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
|
||||
@@ -121,7 +121,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[Seq[String]];
|
||||
data <- extractFromJsonBody[Seq[String]]
|
||||
issueId <- params("id").toIntOpt
|
||||
} yield {
|
||||
data.map { labelName =>
|
||||
@@ -160,7 +160,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||
*/
|
||||
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[Seq[String]];
|
||||
data <- extractFromJsonBody[Seq[String]]
|
||||
issueId <- params("id").toIntOpt
|
||||
} yield {
|
||||
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
||||
|
||||
@@ -102,17 +102,4 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
|
||||
NoContent()
|
||||
})
|
||||
|
||||
private def getApiMilestone(repository: RepositoryInfo, milestoneId: Int): Option[ApiMilestone] = {
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(p => p._1.milestoneId == milestoneId)
|
||||
.map(
|
||||
milestoneWithIssue =>
|
||||
ApiMilestone(
|
||||
repository.repository,
|
||||
milestoneWithIssue._1,
|
||||
milestoneWithIssue._2,
|
||||
milestoneWithIssue._3
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiGroup, CreateAGroup, ApiRepository, ApiUser, JsonFormat}
|
||||
import gitbucket.core.api.{ApiGroup, CreateAGroup, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package gitbucket.core.controller.api
|
||||
import java.io.{ByteArrayInputStream, File}
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
@@ -7,9 +7,8 @@ import gitbucket.core.service.{AccountService, ReleaseService}
|
||||
import gitbucket.core.util.Directory.getReleaseFilesDir
|
||||
import gitbucket.core.util.{FileUtil, ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.{Created, NoContent}
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiReleaseControllerBase extends ControllerBase {
|
||||
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
@@ -87,7 +86,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
||||
/**
|
||||
* vi. Edit a release
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
@@ -104,7 +103,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
||||
/**
|
||||
* vii. Delete a release
|
||||
* https://developer.github.com/v3/repos/releases/#delete-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
@@ -120,41 +119,40 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
||||
* ix. Upload a release asset
|
||||
* https://developer.github.com/v3/repos/releases/#upload-a-release-asset
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { repository =>
|
||||
val name = params("name")
|
||||
val tag = params("tag")
|
||||
getRelease(repository.owner, repository.name, tag)
|
||||
.map {
|
||||
release =>
|
||||
defining(FileUtil.generateFileId) { fileId =>
|
||||
val buf = new Array[Byte](request.inputStream.available())
|
||||
request.inputStream.read(buf)
|
||||
FileUtils.writeByteArrayToFile(
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tag + "/" + fileId)
|
||||
),
|
||||
buf
|
||||
)
|
||||
createReleaseAsset(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
tag,
|
||||
fileId,
|
||||
name,
|
||||
request.contentLength.getOrElse(0),
|
||||
context.loginAccount.get
|
||||
)
|
||||
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
||||
.map { asset =>
|
||||
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
ApiError("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly {
|
||||
repository =>
|
||||
val name = params("name")
|
||||
val tag = params("tag")
|
||||
getRelease(repository.owner, repository.name, tag)
|
||||
.map { release =>
|
||||
val fileId = FileUtil.generateFileId
|
||||
val buf = new Array[Byte](request.inputStream.available())
|
||||
request.inputStream.read(buf)
|
||||
FileUtils.writeByteArrayToFile(
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tag + "/" + fileId)
|
||||
),
|
||||
buf
|
||||
)
|
||||
createReleaseAsset(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
tag,
|
||||
fileId,
|
||||
name,
|
||||
request.contentLength.getOrElse(0),
|
||||
context.loginAccount.get
|
||||
)
|
||||
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
||||
.map { asset =>
|
||||
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
ApiError("Unknown error")
|
||||
}
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
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, commitInfo.id.toString).size
|
||||
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiContents, ApiError, CreateAFile, JsonFormat}
|
||||
import gitbucket.core.api.{ApiCommit, ApiContents, ApiError, CreateAFile, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
|
||||
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList, getSummaryMessage}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -36,7 +35,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* ii. Get contents
|
||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||
* https://docs.github.com/en/rest/reference/repos#get-repository-content
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
||||
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
||||
@@ -44,47 +43,50 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* ii. Get contents
|
||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||
* https://docs.github.com/en/rest/reference/repos#get-repository-content
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||
})
|
||||
|
||||
private def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||
getPathObjectId(git, pathStr, revCommit).map { objectId =>
|
||||
FileInfo(
|
||||
id = objectId,
|
||||
isDirectory = false,
|
||||
name = pathStr.split("/").last,
|
||||
path = pathStr.split("/").dropRight(1).mkString("/"),
|
||||
message = getSummaryMessage(revCommit.getFullMessage, revCommit.getShortMessage),
|
||||
commitId = revCommit.getName,
|
||||
time = revCommit.getAuthorIdent.getWhen,
|
||||
author = revCommit.getAuthorIdent.getName,
|
||||
mailAddress = revCommit.getAuthorIdent.getEmailAddress,
|
||||
linkUrl = None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def getContents(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
path: String,
|
||||
refStr: String,
|
||||
ignoreCase: Boolean = false
|
||||
) = {
|
||||
def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = {
|
||||
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
||||
case -1 =>
|
||||
(".", pathStr)
|
||||
case n =>
|
||||
(pathStr.take(n), pathStr.drop(n + 1))
|
||||
}
|
||||
if (ignoreCase) {
|
||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||
.find(_.name.toLowerCase.equals(fileName.toLowerCase))
|
||||
} else {
|
||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||
.find(_.name.equals(fileName))
|
||||
}
|
||||
}
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||
if (fileList.isEmpty) { // file or NotFound
|
||||
getFileInfo(git, refStr, path, ignoreCase)
|
||||
.flatMap { f =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(refStr))
|
||||
getPathObjectId(git, path, revCommit)
|
||||
.flatMap { objectId =>
|
||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||
val content = getContentFromId(git, f.id, largeFile)
|
||||
val content = getContentFromId(git, objectId, 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) => {
|
||||
case "application/vnd.github.v3.html" if isRenderable(path) => {
|
||||
contentType = "application/vnd.github.v3.html"
|
||||
content.map { c =>
|
||||
List(
|
||||
@@ -115,11 +117,12 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
||||
getFileInfo(git, refStr, path).map { f =>
|
||||
JsonFormat(ApiContents(f, RepositoryName(repository), content))
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
|
||||
} else { // directory
|
||||
JsonFormat(fileList.map { f =>
|
||||
ApiContents(f, RepositoryName(repository), None)
|
||||
@@ -127,56 +130,86 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
/**
|
||||
* iii. Create a file or iv. Update a file
|
||||
* https://developer.github.com/v3/repos/contents/#create-a-file
|
||||
* https://developer.github.com/v3/repos/contents/#update-a-file
|
||||
* https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
|
||||
* if sha is presented, update a file else create a file.
|
||||
* requested #2112
|
||||
*/
|
||||
|
||||
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[CreateAFile]
|
||||
} yield {
|
||||
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
|
||||
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
revCommit.name
|
||||
}
|
||||
val paths = multiParams("splat").head.split("/")
|
||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||
if (data.sha.isDefined && data.sha.get != commit) {
|
||||
ApiError("The blob SHA is not matched.", Some("https://developer.github.com/v3/repos/contents/#update-a-file"))
|
||||
} else {
|
||||
val objectId = commitFile(
|
||||
repository,
|
||||
branch,
|
||||
path,
|
||||
Some(paths.last),
|
||||
data.sha.map(_ => paths.last),
|
||||
StringUtil.base64Decode(data.content),
|
||||
data.message,
|
||||
commit,
|
||||
context.loginAccount.get,
|
||||
data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName),
|
||||
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress),
|
||||
context.settings
|
||||
)
|
||||
ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository))
|
||||
}
|
||||
})
|
||||
context.withLoginAccount {
|
||||
loginAccount =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[CreateAFile]
|
||||
} yield {
|
||||
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
|
||||
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
revCommit.name
|
||||
}
|
||||
val paths = multiParams("splat").head.split("/")
|
||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
|
||||
git =>
|
||||
getFileInfo(git, commit, path) match {
|
||||
case Some(f) if !data.sha.contains(f.id.getName) =>
|
||||
ApiError(
|
||||
"The blob SHA is not matched.",
|
||||
Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
|
||||
)
|
||||
case _ =>
|
||||
val (commitId, blobId) = commitFile(
|
||||
repository,
|
||||
branch,
|
||||
path,
|
||||
Some(paths.last),
|
||||
data.sha.map(_ => paths.last),
|
||||
StringUtil.base64Decode(data.content),
|
||||
data.message,
|
||||
commit,
|
||||
loginAccount,
|
||||
data.committer.map(_.name).getOrElse(loginAccount.fullName),
|
||||
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
|
||||
context.settings
|
||||
)
|
||||
|
||||
blobId match {
|
||||
case None =>
|
||||
ApiError("Failed to commit a file.", None)
|
||||
case Some(blobId) =>
|
||||
Map(
|
||||
"content" -> ApiContents(
|
||||
"file",
|
||||
paths.last,
|
||||
path,
|
||||
blobId.name,
|
||||
Some(data.content),
|
||||
Some("base64")
|
||||
)(RepositoryName(repository)),
|
||||
"commit" -> ApiCommit(
|
||||
git,
|
||||
RepositoryName(repository),
|
||||
new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Delete a file
|
||||
* https://developer.github.com/v3/repos/contents/#delete-a-file
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-a-file
|
||||
* should be implemented
|
||||
*/
|
||||
|
||||
/*
|
||||
* vi. Get archive link
|
||||
* https://developer.github.com/v3/repos/contents/#get-archive-link
|
||||
* vi. Download a repository archive (tar/zip)
|
||||
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar
|
||||
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import gitbucket.core.util._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.Forbidden
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration.Duration
|
||||
@@ -26,7 +27,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* i. List your repositories
|
||||
* https://developer.github.com/v3/repos/#list-your-repositories
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repositories-for-the-authenticated-user
|
||||
*/
|
||||
get("/api/v3/user/repos")(usersOnly {
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
||||
@@ -36,7 +37,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* ii. List user repositories
|
||||
* https://developer.github.com/v3/repos/#list-user-repositories
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repositories-for-a-user
|
||||
*/
|
||||
get("/api/v3/users/:userName/repos") {
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
||||
@@ -46,7 +47,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* iii. List organization repositories
|
||||
* https://developer.github.com/v3/repos/#list-organization-repositories
|
||||
* https://docs.github.com/en/rest/reference/repos#list-organization-repositories
|
||||
*/
|
||||
get("/api/v3/orgs/:orgName/repos") {
|
||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
||||
@@ -56,7 +57,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* iv. List all public repositories
|
||||
* https://developer.github.com/v3/repos/#list-public-repositories
|
||||
* https://docs.github.com/en/rest/reference/repos#list-public-repositories
|
||||
*/
|
||||
get("/api/v3/repositories") {
|
||||
JsonFormat(getPublicRepositories().map { r =>
|
||||
@@ -66,13 +67,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/*
|
||||
* 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
|
||||
* https://docs.github.com/en/rest/reference/repos#create-a-repository-for-the-authenticated-user
|
||||
*/
|
||||
post("/api/v3/user/repos")(usersOnly {
|
||||
val owner = context.loginAccount.get.userName
|
||||
@@ -80,7 +80,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(s"${owner}/${data.name}") {
|
||||
if (getRepository(owner, data.name).isEmpty) {
|
||||
if (getRepository(owner, data.name).isDefined) {
|
||||
ApiError(
|
||||
"A repository with this name already exists on this account",
|
||||
Some("https://developer.github.com/v3/repos/#create")
|
||||
)
|
||||
} else {
|
||||
val f = createRepository(
|
||||
context.loginAccount.get,
|
||||
owner,
|
||||
@@ -95,11 +100,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
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()
|
||||
@@ -107,15 +107,22 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Create group repository
|
||||
* https://developer.github.com/v3/repos/#create
|
||||
* https://docs.github.com/en/rest/reference/repos#create-an-organization-repository
|
||||
*/
|
||||
post("/api/v3/orgs/:org/repos")(managersOnly {
|
||||
post("/api/v3/orgs/:org/repos")(usersOnly {
|
||||
val groupName = params("org")
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(s"${groupName}/${data.name}") {
|
||||
if (getRepository(groupName, data.name).isEmpty) {
|
||||
if (getRepository(groupName, data.name).isDefined) {
|
||||
ApiError(
|
||||
"A repository with this name already exists for this group",
|
||||
Some("https://developer.github.com/v3/repos/#create")
|
||||
)
|
||||
} else if (!canCreateRepository(groupName, context.loginAccount.get)) {
|
||||
Forbidden()
|
||||
} else {
|
||||
val f = createRepository(
|
||||
context.loginAccount.get,
|
||||
groupName,
|
||||
@@ -129,11 +136,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
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()
|
||||
@@ -141,7 +143,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/*
|
||||
* vi. Get
|
||||
* https://developer.github.com/v3/repos/#get
|
||||
* https://docs.github.com/en/rest/reference/repos#get-a-repository
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
||||
@@ -149,32 +151,32 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/*
|
||||
* vii. Edit
|
||||
* https://developer.github.com/v3/repos/#edit
|
||||
* https://docs.github.com/en/rest/reference/repos#update-a-repository
|
||||
*/
|
||||
|
||||
/*
|
||||
* viii. List all topics for a repository
|
||||
* https://developer.github.com/v3/repos/#list-all-topics-for-a-repository
|
||||
* https://docs.github.com/en/rest/reference/repos#get-all-repository-topics
|
||||
*/
|
||||
|
||||
/*
|
||||
* ix. Replace all topics for a repository
|
||||
* https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository
|
||||
* https://docs.github.com/en/rest/reference/repos#replace-all-repository-topics
|
||||
*/
|
||||
|
||||
/*
|
||||
* x. List contributors
|
||||
* https://developer.github.com/v3/repos/#list-contributors
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repository-contributors
|
||||
*/
|
||||
|
||||
/*
|
||||
* xi. List languages
|
||||
* https://developer.github.com/v3/repos/#list-languages
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repository-languages
|
||||
*/
|
||||
|
||||
/*
|
||||
* xii. List teams
|
||||
* https://developer.github.com/v3/repos/#list-teams
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repository-teams
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -189,12 +191,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
|
||||
/*
|
||||
* xiv. Delete a repository
|
||||
* https://developer.github.com/v3/repos/#delete-a-repository
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-a-repository
|
||||
*/
|
||||
|
||||
/*
|
||||
* xv. Transfer a repository
|
||||
* https://developer.github.com/v3/repos/#transfer-a-repository
|
||||
* https://docs.github.com/en/rest/reference/repos#transfer-a-repository
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,7 +104,7 @@ trait ApiUserControllerBase extends ControllerBase {
|
||||
*/
|
||||
delete("/api/v3/users/:userName/suspended")(adminOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName) match {
|
||||
getAccountByUserName(userName, true) match {
|
||||
case Some(targetAccount) =>
|
||||
updateAccount(targetAccount.copy(isRemoved = false))
|
||||
NoContent()
|
||||
|
||||
@@ -3,7 +3,7 @@ package gitbucket.core.model
|
||||
trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
private implicit val whContentTypeColumnType =
|
||||
private implicit val whContentTypeColumnType: BaseColumnType[WebHookContentType] =
|
||||
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
||||
|
||||
lazy val AccountWebHooks = TableQuery[AccountWebHooks]
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait AccountWebHookEventComponent extends TemplateComponent {
|
||||
self: Profile =>
|
||||
|
||||
trait AccountWebHookEventComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import gitbucket.core.model.Profile.AccountWebHooks
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
|
||||
implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i))
|
||||
implicit val commitStateColumnType: BaseColumnType[CommitState] =
|
||||
MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i))
|
||||
|
||||
lazy val CommitStatuses = TableQuery[CommitStatuses]
|
||||
class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate {
|
||||
@@ -77,7 +78,7 @@ object CommitState {
|
||||
|
||||
val values: Vector[CommitState] = Vector(PENDING, SUCCESS, ERROR, FAILURE)
|
||||
|
||||
private val map: Map[String, CommitState] = values.map(enum => enum.name -> enum).toMap
|
||||
private val map: Map[String, CommitState] = values.map(e => e.name -> e).toMap
|
||||
|
||||
def apply(name: String): CommitState = map(name)
|
||||
|
||||
|
||||
@@ -10,15 +10,17 @@ trait Profile {
|
||||
/**
|
||||
* java.util.Date Mapped Column Types
|
||||
*/
|
||||
implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
||||
d => new java.sql.Timestamp(d.getTime),
|
||||
t => new java.util.Date(t.getTime)
|
||||
)
|
||||
implicit val dateColumnType: BaseColumnType[java.util.Date] =
|
||||
MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
||||
d => new java.sql.Timestamp(d.getTime),
|
||||
t => new java.util.Date(t.getTime)
|
||||
)
|
||||
|
||||
/**
|
||||
* WebHookBase.Event Column Types
|
||||
*/
|
||||
implicit val eventColumnType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
|
||||
implicit val eventColumnType: BaseColumnType[WebHook.Event] =
|
||||
MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
|
||||
|
||||
/**
|
||||
* Extends Column to add conditional condition
|
||||
|
||||
@@ -2,9 +2,7 @@ package gitbucket.core.model
|
||||
|
||||
import java.util.Date
|
||||
|
||||
trait ReleaseAssetComponent extends TemplateComponent {
|
||||
self: Profile =>
|
||||
|
||||
trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait ReleaseTagComponent extends TemplateComponent {
|
||||
self: Profile =>
|
||||
|
||||
trait ReleaseTagComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||
val allowFork = column[Boolean]("ALLOW_FORK")
|
||||
val mergeOptions = column[String]("MERGE_OPTIONS")
|
||||
val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION")
|
||||
val safeMode = column[Boolean]("SAFE_MODE")
|
||||
|
||||
def * =
|
||||
(
|
||||
@@ -41,7 +42,16 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||
parentUserName.?,
|
||||
parentRepositoryName.?
|
||||
),
|
||||
(issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork, mergeOptions, defaultMergeOption)
|
||||
(
|
||||
issuesOption,
|
||||
externalIssuesUrl.?,
|
||||
wikiOption,
|
||||
externalWikiUrl.?,
|
||||
allowFork,
|
||||
mergeOptions,
|
||||
defaultMergeOption,
|
||||
safeMode
|
||||
)
|
||||
).shaped.<>(
|
||||
{
|
||||
case (repository, options) =>
|
||||
@@ -112,5 +122,6 @@ case class RepositoryOptions(
|
||||
externalWikiUrl: Option[String],
|
||||
allowFork: Boolean,
|
||||
mergeOptions: String,
|
||||
defaultMergeOption: String
|
||||
defaultMergeOption: String,
|
||||
safeMode: Boolean
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package gitbucket.core.model
|
||||
trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
implicit val whContentTypeColumnType =
|
||||
implicit val whContentTypeColumnType: BaseColumnType[WebHookContentType] =
|
||||
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
||||
|
||||
lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks]
|
||||
|
||||
@@ -8,7 +8,7 @@ object WebHookContentType {
|
||||
|
||||
val values: Vector[WebHookContentType] = Vector(JSON, FORM)
|
||||
|
||||
private val map: Map[String, WebHookContentType] = values.map(enum => enum.code -> enum).toMap
|
||||
private val map: Map[String, WebHookContentType] = values.map(e => e.code -> e).toMap
|
||||
|
||||
def apply(code: String): WebHookContentType = map(code)
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ trait AccessTokenService {
|
||||
* @return (TokenId, Token)
|
||||
*/
|
||||
def generateAccessToken(userName: String, note: String)(implicit s: Session): (Int, String) = {
|
||||
var token: String = null
|
||||
var hash: String = null
|
||||
var token: String = makeAccessTokenString
|
||||
var hash: String = tokenToHash(token)
|
||||
|
||||
do {
|
||||
while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run) {
|
||||
token = makeAccessTokenString
|
||||
hash = tokenToHash(token)
|
||||
} while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run)
|
||||
}
|
||||
|
||||
val newToken = AccessToken(userName = userName, note = note, tokenHash = hash)
|
||||
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) insert newToken
|
||||
|
||||
@@ -47,7 +47,7 @@ trait AccountService {
|
||||
case _ => None
|
||||
}
|
||||
case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account)
|
||||
} getOrElse None
|
||||
}.flatten
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.Directory._
|
||||
import org.json4s._
|
||||
import org.json4s.jackson.Serialization
|
||||
@@ -11,10 +9,8 @@ import org.json4s.jackson.Serialization.{read, write}
|
||||
import scala.util.Using
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.activity.BaseActivityInfo
|
||||
import org.apache.commons.io.input.ReversedLinesFileReader
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
@@ -22,7 +18,7 @@ import scala.collection.mutable.ListBuffer
|
||||
trait ActivityService {
|
||||
self: RequestCache =>
|
||||
|
||||
private implicit val formats = Serialization.formats(NoTypeHints)
|
||||
private implicit val formats: Formats = Serialization.formats(NoTypeHints)
|
||||
|
||||
private def writeLog(activity: Activity): Unit = {
|
||||
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
|
||||
@@ -43,9 +39,7 @@ trait ActivityService {
|
||||
if (isPublic == false) {
|
||||
list += activity
|
||||
} else {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
@@ -65,9 +59,7 @@ trait ActivityService {
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
@@ -87,9 +79,7 @@ trait ActivityService {
|
||||
val activity = read[Activity](json)
|
||||
if (owners.contains(activity.userName)) {
|
||||
list += activity
|
||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import gitbucket.core.model.activity.{
|
||||
}
|
||||
import gitbucket.core.plugin.{IssueHook, PluginRegistry}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
|
||||
trait HandleCommentService {
|
||||
@@ -34,105 +33,104 @@ trait HandleCommentService {
|
||||
actionOpt: Option[String]
|
||||
)(implicit context: Context, s: Session) = {
|
||||
context.loginAccount.flatMap { loginAccount =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
val userName = loginAccount.userName
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val userName = loginAccount.userName
|
||||
|
||||
actionOpt.collect {
|
||||
case "close" if !issue.closed =>
|
||||
updateClosed(owner, name, issue.issueId, true)
|
||||
case "reopen" if issue.closed =>
|
||||
updateClosed(owner, name, issue.issueId, false)
|
||||
}
|
||||
|
||||
val (action, _) = actionOpt
|
||||
.collect {
|
||||
case "close" if !issue.closed =>
|
||||
val info = if (issue.isPullRequest) {
|
||||
ClosePullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
} else {
|
||||
CloseIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
}
|
||||
recordActivity(info)
|
||||
Some("close") -> info
|
||||
case "reopen" if issue.closed =>
|
||||
val info = if (issue.isPullRequest) {
|
||||
ReopenPullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
} else {
|
||||
ReopenIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
}
|
||||
recordActivity(info)
|
||||
Some("reopen") -> info
|
||||
}
|
||||
.getOrElse(None -> None)
|
||||
|
||||
val commentId = (content, action) match {
|
||||
case (None, None) => None
|
||||
case (None, Some(action)) =>
|
||||
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
|
||||
case (Some(content), _) =>
|
||||
val id = Some(
|
||||
createComment(
|
||||
owner,
|
||||
name,
|
||||
userName,
|
||||
issue.issueId,
|
||||
content,
|
||||
action.map(_ + "_comment").getOrElse("comment")
|
||||
)
|
||||
)
|
||||
|
||||
// record comment activity
|
||||
val commentInfo = if (issue.isPullRequest) {
|
||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
} else {
|
||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
}
|
||||
recordActivity(commentInfo)
|
||||
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content, loginAccount)
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
// call web hooks
|
||||
action match {
|
||||
case None =>
|
||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||
case Some(act) =>
|
||||
val webHookAction = act match {
|
||||
case "close" => "closed"
|
||||
case "reopen" => "reopened"
|
||||
}
|
||||
if (issue.isPullRequest)
|
||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount, context.settings)
|
||||
else
|
||||
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
|
||||
}
|
||||
|
||||
// call hooks
|
||||
content foreach { x =>
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
||||
}
|
||||
action foreach {
|
||||
case "close" =>
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository))
|
||||
case "reopen" =>
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository))
|
||||
}
|
||||
|
||||
commentId.map(issue -> _)
|
||||
actionOpt.collect {
|
||||
case "close" if !issue.closed =>
|
||||
updateClosed(owner, name, issue.issueId, true)
|
||||
case "reopen" if issue.closed =>
|
||||
updateClosed(owner, name, issue.issueId, false)
|
||||
}
|
||||
|
||||
val (action, _) = actionOpt
|
||||
.collect {
|
||||
case "close" if !issue.closed =>
|
||||
val info = if (issue.isPullRequest) {
|
||||
ClosePullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
} else {
|
||||
CloseIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
}
|
||||
recordActivity(info)
|
||||
Some("close") -> info
|
||||
case "reopen" if issue.closed =>
|
||||
val info = if (issue.isPullRequest) {
|
||||
ReopenPullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
} else {
|
||||
ReopenIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
}
|
||||
recordActivity(info)
|
||||
Some("reopen") -> info
|
||||
}
|
||||
.getOrElse(None -> None)
|
||||
|
||||
val commentId = (content, action) match {
|
||||
case (None, None) => None
|
||||
case (None, Some(action)) =>
|
||||
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
|
||||
case (Some(content), _) =>
|
||||
val id = Some(
|
||||
createComment(
|
||||
owner,
|
||||
name,
|
||||
userName,
|
||||
issue.issueId,
|
||||
content,
|
||||
action.map(_ + "_comment").getOrElse("comment")
|
||||
)
|
||||
)
|
||||
|
||||
// record comment activity
|
||||
val commentInfo = if (issue.isPullRequest) {
|
||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
} else {
|
||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
}
|
||||
recordActivity(commentInfo)
|
||||
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content, loginAccount)
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
// call web hooks
|
||||
action match {
|
||||
case None =>
|
||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||
case Some(act) =>
|
||||
val webHookAction = act match {
|
||||
case "close" => "closed"
|
||||
case "reopen" => "reopened"
|
||||
}
|
||||
if (issue.isPullRequest)
|
||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount, context.settings)
|
||||
else
|
||||
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
|
||||
}
|
||||
|
||||
// call hooks
|
||||
content foreach { x =>
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
||||
}
|
||||
action foreach {
|
||||
case "close" =>
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository))
|
||||
case "reopen" =>
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository))
|
||||
}
|
||||
|
||||
commentId.map(issue -> _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,33 +159,32 @@ trait HandleCommentService {
|
||||
content: Option[String]
|
||||
)(implicit context: Context, s: Session): Option[(Issue, Int)] = {
|
||||
context.loginAccount.flatMap { loginAccount =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
val userName = loginAccount.userName
|
||||
content match {
|
||||
case Some(content) =>
|
||||
// Update comment
|
||||
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
|
||||
// Record comment activity
|
||||
val commentInfo = if (issue.isPullRequest) {
|
||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
} else {
|
||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
}
|
||||
recordActivity(commentInfo)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content, loginAccount)
|
||||
// call web hooks
|
||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||
// call hooks
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks
|
||||
.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||
_commentId.map(issue -> _)
|
||||
case _ => None
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val userName = loginAccount.userName
|
||||
content match {
|
||||
case Some(content) =>
|
||||
// Update comment
|
||||
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
|
||||
// Record comment activity
|
||||
val commentInfo = if (issue.isPullRequest) {
|
||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
} else {
|
||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
}
|
||||
recordActivity(commentInfo)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content, loginAccount)
|
||||
// call web hooks
|
||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||
// call hooks
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks
|
||||
.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||
_commentId.map(issue -> _)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,7 @@ import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{
|
||||
Account,
|
||||
CommitState,
|
||||
Issue,
|
||||
IssueComment,
|
||||
IssueLabel,
|
||||
Label,
|
||||
PullRequest,
|
||||
Repository,
|
||||
Role
|
||||
}
|
||||
import gitbucket.core.model.{Account, Issue, IssueComment, IssueLabel, Label, PullRequest, Repository, Role}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
@@ -346,13 +336,16 @@ trait IssuesService {
|
||||
implicit s: Session
|
||||
) =
|
||||
Issues filter { t1 =>
|
||||
(if (repos.size == 1) {
|
||||
(if (repos.sizeIs == 1) {
|
||||
t1.byRepository(repos.head._1, repos.head._2)
|
||||
} else {
|
||||
((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" }))
|
||||
}) &&
|
||||
(t1.closed === (condition.state == "closed").bind)
|
||||
.&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
||||
(condition.state match {
|
||||
case "open" => t1.closed === false
|
||||
case "closed" => t1.closed === true
|
||||
case _ => t1.closed === true || t1.closed === false
|
||||
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
||||
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
|
||||
.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
|
||||
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||
@@ -778,16 +771,37 @@ trait IssuesService {
|
||||
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(
|
||||
implicit s: Session
|
||||
): Unit = {
|
||||
extractIssueId(message).foreach { issueId =>
|
||||
val content = s"${fromIssue.issueId}:${fromIssue.title}"
|
||||
if (getIssue(owner, repository, issueId).isDefined) {
|
||||
// Not add if refer comment already exist.
|
||||
if (!getComments(owner, repository, issueId.toInt).exists { x =>
|
||||
x.action == "refer" && x.content == content
|
||||
}) {
|
||||
createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer")
|
||||
extractGlobalIssueId(message).foreach {
|
||||
case (_referredOwner, _referredRepository, referredIssueId) =>
|
||||
val referredOwner = _referredOwner.getOrElse(owner)
|
||||
val referredRepository = _referredRepository.getOrElse(repository)
|
||||
getRepository(referredOwner, referredRepository).foreach { repo =>
|
||||
if (isReadable(repo.repository, Option(loginAccount))) {
|
||||
getIssue(referredOwner, referredRepository, referredIssueId.get).foreach { _ =>
|
||||
val (content, action) = if (owner == referredOwner && repository == referredRepository) {
|
||||
(s"${fromIssue.issueId}:${fromIssue.title}", "refer")
|
||||
} else {
|
||||
(s"${fromIssue.issueId}:${owner}:${repository}:${fromIssue.title}", "refer_global")
|
||||
}
|
||||
referredIssueId.foreach(
|
||||
x =>
|
||||
// Not add if refer comment already exist.
|
||||
if (!getComments(referredOwner, referredRepository, x.toInt).exists { x =>
|
||||
(x.action == "refer" || x.action == "refer_global") && x.content == content
|
||||
}) {
|
||||
createComment(
|
||||
referredOwner,
|
||||
referredRepository,
|
||||
loginAccount.userName,
|
||||
x.toInt,
|
||||
content,
|
||||
action
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -928,7 +942,7 @@ object IssuesService {
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
@@ -949,7 +963,7 @@ object IssuesService {
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
|
||||
@@ -72,7 +72,7 @@ trait MergeService {
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
}
|
||||
@@ -90,7 +90,7 @@ trait MergeService {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId =
|
||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), commits)
|
||||
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits)
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
}
|
||||
@@ -108,7 +108,7 @@ trait MergeService {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId =
|
||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
}
|
||||
@@ -648,7 +648,7 @@ object MergeService {
|
||||
}
|
||||
|
||||
// update branch from cache
|
||||
def merge(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
|
||||
def merge(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
@@ -665,7 +665,7 @@ object MergeService {
|
||||
|
||||
// call pre-commit hooks
|
||||
val error = receiveHooks.flatMap { hook =>
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
|
||||
}.headOption
|
||||
|
||||
error.foreach { error =>
|
||||
@@ -683,7 +683,7 @@ object MergeService {
|
||||
objectId
|
||||
}
|
||||
|
||||
def rebase(committer: PersonIdent, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
|
||||
def rebase(committer: PersonIdent, pusher: String, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
@@ -719,7 +719,7 @@ object MergeService {
|
||||
|
||||
// call pre-commit hooks
|
||||
val error = receiveHooks.flatMap { hook =>
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
|
||||
}.headOption
|
||||
|
||||
error.foreach { error =>
|
||||
@@ -738,7 +738,7 @@ object MergeService {
|
||||
objectId
|
||||
}
|
||||
|
||||
def squash(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
|
||||
def squash(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
@@ -769,7 +769,7 @@ object MergeService {
|
||||
|
||||
// call pre-commit hooks
|
||||
val error = receiveHooks.flatMap { hook =>
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
|
||||
}.headOption
|
||||
|
||||
error.foreach { error =>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api.ApiMilestone
|
||||
import gitbucket.core.model.Milestone
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
|
||||
trait MilestonesService {
|
||||
|
||||
@@ -73,4 +75,17 @@ trait MilestonesService {
|
||||
.sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc))
|
||||
.list
|
||||
|
||||
def getApiMilestone(repository: RepositoryInfo, milestoneId: Int)(implicit s: Session): Option[ApiMilestone] = {
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(p => p._1.milestoneId == milestoneId)
|
||||
.map(
|
||||
milestoneWithIssue =>
|
||||
ApiMilestone(
|
||||
repository.repository,
|
||||
milestoneWithIssue._1,
|
||||
milestoneWithIssue._2,
|
||||
milestoneWithIssue._3
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package gitbucket.core.service
|
||||
import gitbucket.core.model.Priority
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.util.StringUtil
|
||||
|
||||
trait PrioritiesService {
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ object ProtectedBranchService {
|
||||
pusher: String,
|
||||
mergePullRequest: Boolean
|
||||
)(implicit session: Session): Option[String] = {
|
||||
if (mergePullRequest == true) {
|
||||
if (mergePullRequest) {
|
||||
None
|
||||
} else {
|
||||
checkBranchProtection(owner, repository, receivePack, command, pusher)
|
||||
@@ -153,9 +153,9 @@ object ProtectedBranchService {
|
||||
Some("Cannot force-push to a protected branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||
unSuccessedContexts(command.getNewId.name) match {
|
||||
case s if s.size == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.size >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
}
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
Some("Cannot delete a protected branch")
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import com.github.difflib.DiffUtils
|
||||
import com.github.difflib.patch.DeltaType
|
||||
import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import difflib.{Delta, DiffUtils}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.controller.Context
|
||||
@@ -243,7 +244,7 @@ trait PullRequestService {
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: String,
|
||||
loginAccount: Account,
|
||||
pusherAccount: Account,
|
||||
action: String,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
@@ -295,7 +296,7 @@ trait PullRequestService {
|
||||
action,
|
||||
getRepository(owner, repository).get,
|
||||
pullreq.requestBranch,
|
||||
loginAccount,
|
||||
pusherAccount,
|
||||
settings
|
||||
)
|
||||
}
|
||||
@@ -441,16 +442,17 @@ trait PullRequestService {
|
||||
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||
case Right(newLine) =>
|
||||
var counter = newLine
|
||||
patch.getDeltas.asScala.filter(_.getOriginal.getPosition < newLine).foreach { delta =>
|
||||
patch.getDeltas.asScala.filter(_.getSource.getPosition < newLine).foreach { delta =>
|
||||
delta.getType match {
|
||||
case Delta.TYPE.CHANGE =>
|
||||
if (delta.getOriginal.getPosition <= newLine - 1 && newLine <= delta.getOriginal.getPosition + delta.getRevised.getLines.size) {
|
||||
case DeltaType.CHANGE =>
|
||||
if (delta.getSource.getPosition <= newLine - 1 && newLine <= delta.getSource.getPosition + delta.getTarget.getLines.size) {
|
||||
counter = -1
|
||||
} else {
|
||||
counter = counter + (delta.getRevised.getLines.size - delta.getOriginal.getLines.size)
|
||||
counter = counter + (delta.getTarget.getLines.size - delta.getSource.getLines.size)
|
||||
}
|
||||
case Delta.TYPE.INSERT => counter = counter + delta.getRevised.getLines.size
|
||||
case Delta.TYPE.DELETE => counter = counter - delta.getOriginal.getLines.size
|
||||
case DeltaType.INSERT => counter = counter + delta.getTarget.getLines.size
|
||||
case DeltaType.DELETE => counter = counter - delta.getSource.getLines.size
|
||||
case DeltaType.EQUAL => // Do nothing
|
||||
}
|
||||
}
|
||||
if (counter >= 0) {
|
||||
@@ -507,10 +509,11 @@ trait PullRequestService {
|
||||
def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])(
|
||||
implicit s: Session
|
||||
): Seq[Comment] = {
|
||||
(commits
|
||||
.map(commit => getCommitComments(userName, repositoryName, commit.id, true))
|
||||
.flatten ++ getComments(userName, repositoryName, issueId))
|
||||
.groupBy {
|
||||
(commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments(
|
||||
userName,
|
||||
repositoryName,
|
||||
issueId
|
||||
)).groupBy {
|
||||
case x: IssueComment => (Some(x.commentId), None, None, None)
|
||||
case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None)
|
||||
case x: CommitComment => (None, x.fileName, x.originalOldLine, x.originalNewLine)
|
||||
|
||||
@@ -23,23 +23,29 @@ trait RepositoryCommitFileService {
|
||||
with PullRequestService
|
||||
with WebHookPullRequestService
|
||||
with RepositoryService =>
|
||||
import RepositoryCommitFileService._
|
||||
|
||||
/**
|
||||
* Create multiple files by callback function.
|
||||
* Returns commitId.
|
||||
*/
|
||||
def commitFiles(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
files: Seq[CommitFile],
|
||||
branch: String,
|
||||
path: String,
|
||||
message: String,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
// prepend path to the filename
|
||||
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(f)
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
_createFiles(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(
|
||||
f
|
||||
)._1
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file from string content.
|
||||
* Returns commitId + blobId.
|
||||
*/
|
||||
def commitFile(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
@@ -52,7 +58,7 @@ trait RepositoryCommitFileService {
|
||||
commit: String,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
|
||||
commitFile(
|
||||
repository,
|
||||
branch,
|
||||
@@ -69,6 +75,10 @@ trait RepositoryCommitFileService {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file from byte array content.
|
||||
* Returns commitId + blobId.
|
||||
*/
|
||||
def commitFile(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
@@ -78,11 +88,11 @@ trait RepositoryCommitFileService {
|
||||
content: Array[Byte],
|
||||
message: String,
|
||||
commit: String,
|
||||
loginAccount: Account,
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
pusherAccount: Account,
|
||||
committerName: String,
|
||||
committerMailAddress: String,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
|
||||
|
||||
val newPath = newFileName.map { newFileName =>
|
||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||
@@ -91,7 +101,7 @@ trait RepositoryCommitFileService {
|
||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||
}
|
||||
|
||||
_commitFile(repository, branch, message, loginAccount, fullName, mailAddress, settings) {
|
||||
_createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
if (headTip.getName == commit) {
|
||||
val permission = JGitUtil
|
||||
@@ -105,28 +115,33 @@ trait RepositoryCommitFileService {
|
||||
}
|
||||
.flatten
|
||||
.headOption
|
||||
|
||||
newPath.foreach { newPath =>
|
||||
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
|
||||
.map { bits =>
|
||||
FileMode.fromBits(bits)
|
||||
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content)))
|
||||
}
|
||||
.getOrElse(FileMode.REGULAR_FILE)
|
||||
|
||||
val objectId = newPath.map { newPath =>
|
||||
val objectId = inserter.insert(Constants.OBJ_BLOB, content)
|
||||
builder.add(JGitUtil.createDirCacheEntry(newPath, permission, objectId))
|
||||
objectId
|
||||
}
|
||||
builder.finish()
|
||||
}
|
||||
objectId
|
||||
} else None
|
||||
}
|
||||
}
|
||||
|
||||
private def _commitFile(
|
||||
private def _createFiles[R](
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
message: String,
|
||||
loginAccount: Account,
|
||||
pusherAccount: Account,
|
||||
committerName: String,
|
||||
committerMailAddress: String,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
|
||||
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, R) = {
|
||||
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
@@ -135,7 +150,7 @@ trait RepositoryCommitFileService {
|
||||
val headName = s"refs/heads/${branch}"
|
||||
val headTip = git.getRepository.resolve(headName)
|
||||
|
||||
f(git, headTip, builder, inserter)
|
||||
val result = f(git, headTip, builder, inserter)
|
||||
|
||||
val commitId = JGitUtil.createNewCommit(
|
||||
git,
|
||||
@@ -156,7 +171,7 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call pre-commit hook
|
||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false)
|
||||
}.headOption
|
||||
|
||||
error match {
|
||||
@@ -177,12 +192,12 @@ trait RepositoryCommitFileService {
|
||||
refUpdate.update()
|
||||
|
||||
// update pull request
|
||||
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize", settings)
|
||||
updatePullRequests(repository.owner, repository.name, branch, pusherAccount, "synchronize", settings)
|
||||
|
||||
// record activity
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
val pushInfo = PushInfo(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
|
||||
val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||
recordActivity(pushInfo)
|
||||
|
||||
// create issue comment by commit message
|
||||
@@ -192,17 +207,17 @@ trait RepositoryCommitFileService {
|
||||
if (branch == repository.repository.defaultBranch) {
|
||||
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, settings)
|
||||
callIssuesWebHook("closed", repository, issue, pusherAccount, settings)
|
||||
val closeIssueInfo = CloseIssueInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
pusherAccount.userName,
|
||||
issue.issueId,
|
||||
issue.title
|
||||
)
|
||||
recordActivity(closeIssueInfo)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, pusherAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +232,7 @@ trait RepositoryCommitFileService {
|
||||
getAccountByUserName(repository.owner).map { ownerAccount =>
|
||||
WebHookPushPayload(
|
||||
git,
|
||||
loginAccount,
|
||||
pusherAccount,
|
||||
headName,
|
||||
repository,
|
||||
List(commit),
|
||||
@@ -228,7 +243,7 @@ trait RepositoryCommitFileService {
|
||||
}
|
||||
}
|
||||
}
|
||||
commitId
|
||||
(commitId, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ object RepositoryCreationService {
|
||||
private val Creating = new ConcurrentHashMap[String, Option[String]]()
|
||||
|
||||
def isCreating(owner: String, repository: String): Boolean = {
|
||||
Option(Creating.get(s"${owner}/${repository}")).map(_.isEmpty).getOrElse(false)
|
||||
Option(Creating.get(s"${owner}/${repository}")).exists(_.isEmpty)
|
||||
}
|
||||
|
||||
def startCreation(owner: String, repository: String): Unit = {
|
||||
@@ -40,7 +40,7 @@ object RepositoryCreationService {
|
||||
}
|
||||
|
||||
def getCreationError(owner: String, repository: String): Option[String] = {
|
||||
Option(Creating.remove(s"${owner}/${repository}")).getOrElse(None)
|
||||
Option(Creating.remove(s"${owner}/${repository}")).flatten
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,6 +53,11 @@ trait RepositoryCreationService {
|
||||
with ActivityService
|
||||
with PrioritiesService =>
|
||||
|
||||
def canCreateRepository(repositoryOwner: String, loginAccount: Account)(implicit session: Session): Boolean = {
|
||||
repositoryOwner == loginAccount.userName || getGroupsByUserName(loginAccount.userName)
|
||||
.contains(repositoryOwner) || loginAccount.isAdmin
|
||||
}
|
||||
|
||||
def createRepository(
|
||||
loginAccount: Account,
|
||||
owner: String,
|
||||
@@ -76,7 +81,7 @@ trait RepositoryCreationService {
|
||||
RepositoryCreationService.startCreation(owner, name)
|
||||
try {
|
||||
Database() withTransaction { implicit session =>
|
||||
val ownerAccount = getAccountByUserName(owner).get
|
||||
//val ownerAccount = getAccountByUserName(owner).get
|
||||
val loginUserName = loginAccount.userName
|
||||
|
||||
val copyRepositoryDir = if (initOption == "COPY") {
|
||||
|
||||
@@ -2,7 +2,6 @@ package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
@@ -12,7 +11,7 @@ import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, g
|
||||
import gitbucket.core.util.JGitUtil.FileInfo
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.{Repository => _, _}
|
||||
import org.eclipse.jgit.lib.{Repository => _}
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryService {
|
||||
@@ -61,7 +60,8 @@ trait RepositoryService {
|
||||
externalWikiUrl = None,
|
||||
allowFork = true,
|
||||
mergeOptions = "merge-commit,squash,rebase",
|
||||
defaultMergeOption = "merge-commit"
|
||||
defaultMergeOption = "merge-commit",
|
||||
safeMode = true
|
||||
)
|
||||
)
|
||||
|
||||
@@ -202,22 +202,19 @@ trait RepositoryService {
|
||||
)
|
||||
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getRepositoryDir(newUserName, newRepositoryName))
|
||||
}
|
||||
val repoDir = getRepositoryDir(oldUserName, oldRepositoryName)
|
||||
if (repoDir.isDirectory) {
|
||||
FileUtils.moveDirectory(repoDir, getRepositoryDir(newUserName, newRepositoryName))
|
||||
}
|
||||
// Move wiki repository
|
||||
defining(getWikiRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(newUserName, newRepositoryName))
|
||||
}
|
||||
val wikiDir = getWikiRepositoryDir(oldUserName, oldRepositoryName)
|
||||
if (wikiDir.isDirectory) {
|
||||
FileUtils.moveDirectory(wikiDir, getWikiRepositoryDir(newUserName, newRepositoryName))
|
||||
}
|
||||
// Move files directory
|
||||
defining(getRepositoryFilesDir(oldUserName, oldRepositoryName)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
FileUtils.moveDirectory(dir, getRepositoryFilesDir(newUserName, newRepositoryName))
|
||||
}
|
||||
val filesDir = getRepositoryFilesDir(oldUserName, oldRepositoryName)
|
||||
if (filesDir.isDirectory) {
|
||||
FileUtils.moveDirectory(filesDir, getRepositoryFilesDir(newUserName, newRepositoryName))
|
||||
}
|
||||
// Delete parent directory
|
||||
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName))
|
||||
@@ -237,10 +234,11 @@ trait RepositoryService {
|
||||
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))
|
||||
FileUtil.deleteRecursively(getRepositoryDir(repository.userName, repository.repositoryName))
|
||||
|
||||
FileUtil.deleteRecursively(getWikiRepositoryDir(repository.userName, repository.repositoryName))
|
||||
FileUtil.deleteRecursively(getTemporaryDir(repository.userName, repository.repositoryName))
|
||||
FileUtil.deleteRecursively(getRepositoryFilesDir(repository.userName, repository.repositoryName))
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName))
|
||||
@@ -463,7 +461,7 @@ trait RepositoryService {
|
||||
.filter { case (t1, t2) => t2.removed === false.bind }
|
||||
.map { case (t1, t2) => t1 }
|
||||
// for Normal Users
|
||||
case Some(x) if (!x.isAdmin || limit) =>
|
||||
case Some(x) =>
|
||||
Repositories
|
||||
.join(Accounts)
|
||||
.on(_.userName === _.userName)
|
||||
@@ -553,7 +551,8 @@ trait RepositoryService {
|
||||
externalWikiUrl: Option[String],
|
||||
allowFork: Boolean,
|
||||
mergeOptions: Seq[String],
|
||||
defaultMergeOption: String
|
||||
defaultMergeOption: String,
|
||||
safeMode: Boolean
|
||||
)(implicit s: Session): Unit = {
|
||||
|
||||
Repositories
|
||||
@@ -569,6 +568,7 @@ trait RepositoryService {
|
||||
r.allowFork,
|
||||
r.mergeOptions,
|
||||
r.defaultMergeOption,
|
||||
r.safeMode,
|
||||
r.updatedDate
|
||||
)
|
||||
}
|
||||
@@ -582,6 +582,7 @@ trait RepositoryService {
|
||||
allowFork,
|
||||
mergeOptions.mkString(","),
|
||||
defaultMergeOption,
|
||||
safeMode,
|
||||
currentDate
|
||||
)
|
||||
}
|
||||
@@ -765,7 +766,8 @@ trait RepositoryService {
|
||||
JGitUtil.getContentFromId(git, file.id, true).collect {
|
||||
case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes)
|
||||
}
|
||||
} getOrElse None
|
||||
}
|
||||
.flatten
|
||||
} getOrElse ""
|
||||
}
|
||||
}
|
||||
@@ -819,11 +821,13 @@ object RepositoryService {
|
||||
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
|
||||
|
||||
def splitPath(path: String): (String, String) = {
|
||||
val id = branchList.collectFirst {
|
||||
case branch if (path == branch || path.startsWith(branch + "/")) => branch
|
||||
} orElse tags.collectFirst {
|
||||
case tag if (path == tag.name || path.startsWith(tag.name + "/")) => tag.name
|
||||
} getOrElse path.split("/")(0)
|
||||
val names = (branchList ++ tags.map(_.name)).sortBy(_.length).reverse
|
||||
|
||||
val id = names.collectFirst {
|
||||
case name if (path == name || path.startsWith(name + "/")) => name
|
||||
} getOrElse {
|
||||
path.split("/")(0)
|
||||
}
|
||||
|
||||
(id, path.substring(id.length).stripPrefix("/"))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer}
|
||||
import gitbucket.core.service.SystemSettingsService._
|
||||
import gitbucket.core.util.ConfigUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import scala.util.Using
|
||||
|
||||
trait SystemSettingsService {
|
||||
@@ -15,175 +14,173 @@ trait SystemSettingsService {
|
||||
def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
|
||||
|
||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||
defining(new java.util.Properties()) { props =>
|
||||
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
||||
settings.information.foreach(x => props.setProperty(Information, x))
|
||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
|
||||
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
||||
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
|
||||
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
|
||||
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
|
||||
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
|
||||
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
|
||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||
props.setProperty(Notification, settings.notification.toString)
|
||||
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
|
||||
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
|
||||
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
|
||||
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
|
||||
props.setProperty(UseSMTP, settings.useSMTP.toString)
|
||||
if (settings.useSMTP) {
|
||||
settings.smtp.foreach { smtp =>
|
||||
props.setProperty(SmtpHost, smtp.host)
|
||||
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
||||
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
||||
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
||||
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
||||
smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString))
|
||||
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
|
||||
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
|
||||
val props = new java.util.Properties()
|
||||
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
||||
settings.information.foreach(x => props.setProperty(Information, x))
|
||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
|
||||
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
||||
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
|
||||
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
|
||||
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
|
||||
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
|
||||
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
|
||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||
props.setProperty(Notification, settings.notification.toString)
|
||||
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
|
||||
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
|
||||
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
|
||||
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
|
||||
props.setProperty(UseSMTP, settings.useSMTP.toString)
|
||||
if (settings.useSMTP) {
|
||||
settings.smtp.foreach { smtp =>
|
||||
props.setProperty(SmtpHost, smtp.host)
|
||||
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
||||
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
||||
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
||||
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
||||
smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString))
|
||||
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
|
||||
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
|
||||
}
|
||||
}
|
||||
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
||||
if (settings.ldapAuthentication) {
|
||||
settings.ldap.foreach { ldap =>
|
||||
props.setProperty(LdapHost, ldap.host)
|
||||
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
||||
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
||||
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
||||
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
|
||||
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
||||
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
|
||||
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
||||
}
|
||||
}
|
||||
props.setProperty(OidcAuthentication, settings.oidcAuthentication.toString)
|
||||
if (settings.oidcAuthentication) {
|
||||
settings.oidc.foreach { oidc =>
|
||||
props.setProperty(OidcIssuer, oidc.issuer.getValue)
|
||||
props.setProperty(OidcClientId, oidc.clientID.getValue)
|
||||
props.setProperty(OidcClientSecret, oidc.clientSecret.getValue)
|
||||
oidc.jwsAlgorithm.foreach { x =>
|
||||
props.setProperty(OidcJwsAlgorithm, x.getName)
|
||||
}
|
||||
}
|
||||
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
||||
if (settings.ldapAuthentication) {
|
||||
settings.ldap.foreach { ldap =>
|
||||
props.setProperty(LdapHost, ldap.host)
|
||||
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
||||
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
||||
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
||||
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
|
||||
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
||||
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
|
||||
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
||||
}
|
||||
}
|
||||
props.setProperty(OidcAuthentication, settings.oidcAuthentication.toString)
|
||||
if (settings.oidcAuthentication) {
|
||||
settings.oidc.foreach { oidc =>
|
||||
props.setProperty(OidcIssuer, oidc.issuer.getValue)
|
||||
props.setProperty(OidcClientId, oidc.clientID.getValue)
|
||||
props.setProperty(OidcClientSecret, oidc.clientSecret.getValue)
|
||||
oidc.jwsAlgorithm.foreach { x =>
|
||||
props.setProperty(OidcJwsAlgorithm, x.getName)
|
||||
}
|
||||
}
|
||||
}
|
||||
props.setProperty(SkinName, settings.skinName.toString)
|
||||
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
|
||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
|
||||
props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
|
||||
props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
|
||||
props.setProperty(UploadTimeout, settings.upload.timeout.toString)
|
||||
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
|
||||
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
||||
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
||||
}
|
||||
props.setProperty(SkinName, settings.skinName)
|
||||
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
|
||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
|
||||
props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
|
||||
props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
|
||||
props.setProperty(UploadTimeout, settings.upload.timeout.toString)
|
||||
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
|
||||
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
||||
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
||||
|
||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
}
|
||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
}
|
||||
}
|
||||
|
||||
def loadSystemSettings(): SystemSettings = {
|
||||
defining(new java.util.Properties()) { props =>
|
||||
if (GitBucketConf.exists) {
|
||||
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
|
||||
props.load(in)
|
||||
}
|
||||
val props = new java.util.Properties()
|
||||
if (GitBucketConf.exists) {
|
||||
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
|
||||
props.load(in)
|
||||
}
|
||||
SystemSettings(
|
||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||
getOptionValue(props, Information, None),
|
||||
getValue(props, AllowAccountRegistration, false),
|
||||
getValue(props, AllowAnonymousAccess, true),
|
||||
getValue(props, IsCreateRepoOptionPublic, true),
|
||||
RepositoryOperation(
|
||||
create = getValue(props, RepositoryOperationCreate, true),
|
||||
delete = getValue(props, RepositoryOperationDelete, true),
|
||||
rename = getValue(props, RepositoryOperationRename, true),
|
||||
transfer = getValue(props, RepositoryOperationTransfer, true),
|
||||
fork = getValue(props, RepositoryOperationFork, true)
|
||||
),
|
||||
getValue(props, Gravatar, false),
|
||||
getValue(props, Notification, false),
|
||||
getValue(props, LimitVisibleRepositories, false),
|
||||
Ssh(
|
||||
getValue(props, SshEnabled, false),
|
||||
getOptionValue[String](props, SshHost, None).map(_.trim),
|
||||
getOptionValue(props, SshPort, Some(DefaultSshPort))
|
||||
),
|
||||
getValue(
|
||||
props,
|
||||
UseSMTP,
|
||||
getValue(props, Notification, false)
|
||||
), // handle migration scenario from only notification to useSMTP
|
||||
if (getValue(props, UseSMTP, getValue(props, Notification, false))) {
|
||||
Some(
|
||||
Smtp(
|
||||
getValue(props, SmtpHost, ""),
|
||||
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
||||
getOptionValue(props, SmtpUser, None),
|
||||
getOptionValue(props, SmtpPassword, None),
|
||||
getOptionValue[Boolean](props, SmtpSsl, None),
|
||||
getOptionValue[Boolean](props, SmtpStarttls, None),
|
||||
getOptionValue(props, SmtpFromAddress, None),
|
||||
getOptionValue(props, SmtpFromName, None)
|
||||
)
|
||||
)
|
||||
} else None,
|
||||
getValue(props, LdapAuthentication, false),
|
||||
if (getValue(props, LdapAuthentication, false)) {
|
||||
Some(
|
||||
Ldap(
|
||||
getValue(props, LdapHost, ""),
|
||||
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
||||
getOptionValue(props, LdapBindDN, None),
|
||||
getOptionValue(props, LdapBindPassword, None),
|
||||
getValue(props, LdapBaseDN, ""),
|
||||
getValue(props, LdapUserNameAttribute, ""),
|
||||
getOptionValue(props, LdapAdditionalFilterCondition, None),
|
||||
getOptionValue(props, LdapFullNameAttribute, None),
|
||||
getOptionValue(props, LdapMailAddressAttribute, None),
|
||||
getOptionValue[Boolean](props, LdapTls, None),
|
||||
getOptionValue[Boolean](props, LdapSsl, None),
|
||||
getOptionValue(props, LdapKeystore, None)
|
||||
)
|
||||
)
|
||||
} else None,
|
||||
getValue(props, OidcAuthentication, false),
|
||||
if (getValue(props, OidcAuthentication, false)) {
|
||||
Some(
|
||||
OIDC(
|
||||
getValue(props, OidcIssuer, ""),
|
||||
getValue(props, OidcClientId, ""),
|
||||
getValue(props, OidcClientSecret, ""),
|
||||
getOptionValue(props, OidcJwsAlgorithm, None)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
getValue(props, SkinName, "skin-blue"),
|
||||
getOptionValue(props, UserDefinedCss, None),
|
||||
getValue(props, ShowMailAddress, false),
|
||||
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
|
||||
Upload(
|
||||
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadTimeout, 3 * 10000),
|
||||
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadLargeTimeout, 3 * 10000)
|
||||
),
|
||||
RepositoryViewerSettings(
|
||||
getValue(props, RepositoryViewerMaxFiles, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
SystemSettings(
|
||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||
getOptionValue(props, Information, None),
|
||||
getValue(props, AllowAccountRegistration, false),
|
||||
getValue(props, AllowAnonymousAccess, true),
|
||||
getValue(props, IsCreateRepoOptionPublic, true),
|
||||
RepositoryOperation(
|
||||
create = getValue(props, RepositoryOperationCreate, true),
|
||||
delete = getValue(props, RepositoryOperationDelete, true),
|
||||
rename = getValue(props, RepositoryOperationRename, true),
|
||||
transfer = getValue(props, RepositoryOperationTransfer, true),
|
||||
fork = getValue(props, RepositoryOperationFork, true)
|
||||
),
|
||||
getValue(props, Gravatar, false),
|
||||
getValue(props, Notification, false),
|
||||
getValue(props, LimitVisibleRepositories, false),
|
||||
Ssh(
|
||||
getValue(props, SshEnabled, false),
|
||||
getOptionValue[String](props, SshHost, None).map(_.trim),
|
||||
getOptionValue(props, SshPort, Some(DefaultSshPort))
|
||||
),
|
||||
getValue(
|
||||
props,
|
||||
UseSMTP,
|
||||
getValue(props, Notification, false)
|
||||
), // handle migration scenario from only notification to useSMTP
|
||||
if (getValue(props, UseSMTP, getValue(props, Notification, false))) {
|
||||
Some(
|
||||
Smtp(
|
||||
getValue(props, SmtpHost, ""),
|
||||
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
||||
getOptionValue(props, SmtpUser, None),
|
||||
getOptionValue(props, SmtpPassword, None),
|
||||
getOptionValue[Boolean](props, SmtpSsl, None),
|
||||
getOptionValue[Boolean](props, SmtpStarttls, None),
|
||||
getOptionValue(props, SmtpFromAddress, None),
|
||||
getOptionValue(props, SmtpFromName, None)
|
||||
)
|
||||
)
|
||||
} else None,
|
||||
getValue(props, LdapAuthentication, false),
|
||||
if (getValue(props, LdapAuthentication, false)) {
|
||||
Some(
|
||||
Ldap(
|
||||
getValue(props, LdapHost, ""),
|
||||
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
||||
getOptionValue(props, LdapBindDN, None),
|
||||
getOptionValue(props, LdapBindPassword, None),
|
||||
getValue(props, LdapBaseDN, ""),
|
||||
getValue(props, LdapUserNameAttribute, ""),
|
||||
getOptionValue(props, LdapAdditionalFilterCondition, None),
|
||||
getOptionValue(props, LdapFullNameAttribute, None),
|
||||
getOptionValue(props, LdapMailAddressAttribute, None),
|
||||
getOptionValue[Boolean](props, LdapTls, None),
|
||||
getOptionValue[Boolean](props, LdapSsl, None),
|
||||
getOptionValue(props, LdapKeystore, None)
|
||||
)
|
||||
)
|
||||
} else None,
|
||||
getValue(props, OidcAuthentication, false),
|
||||
if (getValue(props, OidcAuthentication, false)) {
|
||||
Some(
|
||||
OIDC(
|
||||
getValue(props, OidcIssuer, ""),
|
||||
getValue(props, OidcClientId, ""),
|
||||
getValue(props, OidcClientSecret, ""),
|
||||
getOptionValue(props, OidcJwsAlgorithm, None)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
getValue(props, SkinName, "skin-blue"),
|
||||
getOptionValue(props, UserDefinedCss, None),
|
||||
getValue(props, ShowMailAddress, false),
|
||||
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
|
||||
Upload(
|
||||
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadTimeout, 3 * 10000),
|
||||
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadLargeTimeout, 3 * 10000)
|
||||
),
|
||||
RepositoryViewerSettings(
|
||||
getValue(props, RepositoryViewerMaxFiles, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -368,12 +365,11 @@ object SystemSettingsService {
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
getConfigValue(key).getOrElse {
|
||||
defining(props.getProperty(key)) { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
} else {
|
||||
convertType(value).asInstanceOf[A]
|
||||
}
|
||||
val value = props.getProperty(key)
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
} else {
|
||||
convertType(value).asInstanceOf[A]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,12 +386,11 @@ object SystemSettingsService {
|
||||
|
||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
|
||||
getConfigValue(key).orElse {
|
||||
defining(props.getProperty(key)) { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
} else {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
}
|
||||
val value = props.getProperty(key)
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
} else {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package gitbucket.core.service
|
||||
import fr.brouillard.oss.security.xhub.XHub
|
||||
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{
|
||||
Account,
|
||||
AccountWebHook,
|
||||
@@ -21,7 +20,6 @@ import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.apache.http.client.utils.URLEncodedUtils
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import org.apache.http.NameValuePair
|
||||
@@ -37,6 +35,7 @@ import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.view.helpers.getApiMilestone
|
||||
import org.apache.http.client.entity.EntityBuilder
|
||||
import org.apache.http.entity.ContentType
|
||||
|
||||
@@ -128,7 +127,7 @@ trait WebHookService {
|
||||
ctype = ctype,
|
||||
token = token
|
||||
)
|
||||
events.map { event: WebHook.Event =>
|
||||
events.map { (event: WebHook.Event) =>
|
||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
@@ -146,7 +145,7 @@ trait WebHookService {
|
||||
.map(w => (w.ctype, w.token))
|
||||
.update((ctype, token))
|
||||
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
||||
events.map { event: WebHook.Event =>
|
||||
events.map { (event: WebHook.Event) =>
|
||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
@@ -165,7 +164,7 @@ trait WebHookService {
|
||||
.map(w => (w.url, w.ctype, w.token))
|
||||
.update((url, ctype, token))
|
||||
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
||||
events.map { event: WebHook.Event =>
|
||||
events.map { (event: WebHook.Event) =>
|
||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
@@ -230,7 +229,7 @@ trait WebHookService {
|
||||
token: Option[String]
|
||||
)(implicit s: Session): Unit = {
|
||||
AccountWebHooks insert AccountWebHook(owner, url, ctype, token)
|
||||
events.map { event: WebHook.Event =>
|
||||
events.map { (event: WebHook.Event) =>
|
||||
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
|
||||
}
|
||||
}
|
||||
@@ -244,7 +243,7 @@ trait WebHookService {
|
||||
)(implicit s: Session): Unit = {
|
||||
AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token))
|
||||
AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete
|
||||
events.map { event: WebHook.Event =>
|
||||
events.map { (event: WebHook.Event) =>
|
||||
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
|
||||
}
|
||||
}
|
||||
@@ -396,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
ApiUser(issueUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
.map(ApiLabel(_, RepositoryName(repository))),
|
||||
getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||
),
|
||||
sender = ApiUser(sender)
|
||||
)
|
||||
@@ -578,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
commenter <- users.get(issueComment.commentedUserName)
|
||||
assignedUser = issue.assignedUserName.flatMap(users.get(_))
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||
} yield {
|
||||
WebHookIssueCommentPayload(
|
||||
issue = issue,
|
||||
@@ -588,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
repositoryUser = repoOwner,
|
||||
assignedUser = assignedUser,
|
||||
sender = sender,
|
||||
labels = labels
|
||||
labels = labels,
|
||||
milestone = milestone
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -762,7 +764,8 @@ object WebHookService {
|
||||
repositoryUser: Account,
|
||||
assignedUser: Option[Account],
|
||||
sender: Account,
|
||||
labels: List[Label]
|
||||
labels: List[Label],
|
||||
milestone: Option[ApiMilestone]
|
||||
): WebHookIssueCommentPayload =
|
||||
WebHookIssueCommentPayload(
|
||||
action = "created",
|
||||
@@ -772,7 +775,8 @@ object WebHookService {
|
||||
RepositoryName(repository),
|
||||
ApiUser(issueUser),
|
||||
assignedUser.map(ApiUser(_)),
|
||||
labels.map(ApiLabel(_, RepositoryName(repository)))
|
||||
labels.map(ApiLabel(_, RepositoryName(repository))),
|
||||
milestone
|
||||
),
|
||||
comment =
|
||||
ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),
|
||||
|
||||
@@ -5,7 +5,6 @@ import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
||||
import org.eclipse.jgit.lib._
|
||||
@@ -54,20 +53,19 @@ trait WikiService {
|
||||
|
||||
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
defining(Directory.getWikiRepositoryDir(owner, repository)) { dir =>
|
||||
if (!dir.exists) {
|
||||
JGitUtil.initRepository(dir)
|
||||
saveWikiPage(
|
||||
owner,
|
||||
repository,
|
||||
"Home",
|
||||
"Home",
|
||||
s"Welcome to the ${repository} wiki!!",
|
||||
loginAccount,
|
||||
"Initial Commit",
|
||||
None
|
||||
)
|
||||
}
|
||||
val dir = Directory.getWikiRepositoryDir(owner, repository)
|
||||
if (!dir.exists) {
|
||||
JGitUtil.initRepository(dir)
|
||||
saveWikiPage(
|
||||
owner,
|
||||
repository,
|
||||
"Home",
|
||||
"Home",
|
||||
s"Welcome to the ${repository} wiki!!",
|
||||
loginAccount,
|
||||
"Initial Commit",
|
||||
None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,13 +75,15 @@ trait WikiService {
|
||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
if (!JGitUtil.isEmpty(git)) {
|
||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||
val fileName = pageName + ".md"
|
||||
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
|
||||
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
|
||||
WikiPageInfo(
|
||||
file.name,
|
||||
StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
|
||||
file.author,
|
||||
file.time,
|
||||
file.commitId
|
||||
fileName,
|
||||
StringUtil.convertFromByteArray(content.getOrElse(Array.empty)),
|
||||
latestCommit.getAuthorIdent.getName,
|
||||
latestCommit.getAuthorIdent.getWhen,
|
||||
latestCommit.getName
|
||||
)
|
||||
}
|
||||
} else None
|
||||
@@ -147,7 +147,7 @@ trait WikiService {
|
||||
if (!p.getErrors.isEmpty) {
|
||||
throw new PatchFormatException(p.getErrors())
|
||||
}
|
||||
val revertInfo = (p.getFiles.asScala.map { fh =>
|
||||
val revertInfo = p.getFiles.asScala.flatMap { fh =>
|
||||
fh.getChangeType match {
|
||||
case DiffEntry.ChangeType.MODIFY => {
|
||||
val source =
|
||||
@@ -176,7 +176,7 @@ trait WikiService {
|
||||
}
|
||||
case _ => Nil
|
||||
}
|
||||
}).flatten
|
||||
}
|
||||
|
||||
if (revertInfo.nonEmpty) {
|
||||
val builder = DirCache.newInCore.builder()
|
||||
@@ -257,8 +257,7 @@ trait WikiService {
|
||||
created = false
|
||||
updated = JGitUtil
|
||||
.getContentFromId(git, tree.getEntryObjectId, true)
|
||||
.map(new String(_, "UTF-8") != content)
|
||||
.getOrElse(false)
|
||||
.exists(new String(_, "UTF-8") != content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
override def destroy(): Unit = {}
|
||||
|
||||
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||
implicit val request = req.asInstanceOf[HttpServletRequest]
|
||||
implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||
implicit val request: HttpServletRequest = req.asInstanceOf[HttpServletRequest]
|
||||
implicit val session: slick.jdbc.JdbcBackend#Session =
|
||||
req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||
val response = res.asInstanceOf[HttpServletResponse]
|
||||
Option(request.getHeader("Authorization"))
|
||||
.map {
|
||||
|
||||
@@ -139,7 +139,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
|
||||
case _ =>
|
||||
() =>
|
||||
{
|
||||
logger.debug(s"Not enough path arguments: ${request.paths}")
|
||||
logger.debug(s"Not enough path arguments: ${request.paths.mkString(", ")}")
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import gitbucket.core.util.{FileUtil, StringUtil}
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.json4s.Formats
|
||||
import org.json4s.jackson.Serialization._
|
||||
import org.apache.http.HttpStatus
|
||||
|
||||
@@ -17,7 +18,7 @@ import scala.util.Using
|
||||
*/
|
||||
class GitLfsTransferServlet extends HttpServlet {
|
||||
|
||||
private implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
private implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
private val LongObjectIdLength = 32
|
||||
private val LongObjectIdStringLength = LongObjectIdLength * 2
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ import java.util.Date
|
||||
|
||||
import scala.util.Using
|
||||
import gitbucket.core.api
|
||||
import gitbucket.core.api.JsonFormat.Context
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
@@ -42,6 +42,7 @@ import javax.servlet.ServletConfig
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||
import org.json4s.Formats
|
||||
import org.json4s.jackson.Serialization._
|
||||
|
||||
/**
|
||||
@@ -53,7 +54,7 @@ import org.json4s.jackson.Serialization._
|
||||
class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[GitRepositoryServlet])
|
||||
private implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
private implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
|
||||
override def init(config: ServletConfig): Unit = {
|
||||
setReceivePackFactory(new GitBucketReceivePackFactory())
|
||||
@@ -200,33 +201,28 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
logger.debug("requestURI: " + request.getRequestURI)
|
||||
logger.debug("pusher:" + pusher)
|
||||
|
||||
defining(request.paths) { paths =>
|
||||
val owner = paths(1)
|
||||
val repository = paths(2).stripSuffix(".git")
|
||||
val paths = request.paths
|
||||
val owner = paths(1)
|
||||
val repository = paths(2).stripSuffix(".git")
|
||||
|
||||
logger.debug("repository:" + owner + "/" + repository)
|
||||
logger.debug("repository:" + owner + "/" + repository)
|
||||
|
||||
val settings = loadSystemSettings()
|
||||
val baseUrl = settings.baseUrl(request)
|
||||
val sshUrl = settings.sshAddress.map { x =>
|
||||
s"${x.genericUser}@${x.host}:${x.port}"
|
||||
}
|
||||
val settings = loadSystemSettings()
|
||||
val baseUrl = settings.baseUrl(request)
|
||||
val sshUrl = settings.sshAddress.map { x =>
|
||||
s"${x.genericUser}@${x.host}:${x.port}"
|
||||
}
|
||||
|
||||
if (!repository.endsWith(".wiki")) {
|
||||
defining(request) { implicit r =>
|
||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
|
||||
receivePack.setPreReceiveHook(hook)
|
||||
receivePack.setPostReceiveHook(hook)
|
||||
}
|
||||
}
|
||||
if (!repository.endsWith(".wiki")) {
|
||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
|
||||
receivePack.setPreReceiveHook(hook)
|
||||
receivePack.setPostReceiveHook(hook)
|
||||
}
|
||||
|
||||
if (repository.endsWith(".wiki")) {
|
||||
defining(request) { implicit r =>
|
||||
receivePack.setPostReceiveHook(
|
||||
new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (repository.endsWith(".wiki")) {
|
||||
receivePack.setPostReceiveHook(
|
||||
new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +289,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
val pushedIds = scala.collection.mutable.Set[String]()
|
||||
commands.asScala.foreach { command =>
|
||||
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
||||
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||
val refName = command.getRefName.split("/")
|
||||
val branchName = refName.drop(2).mkString("/")
|
||||
val commits = if (refName(1) == "tags") {
|
||||
@@ -351,9 +347,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
// set PR as merged
|
||||
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
|
||||
pulls.foreach { pull =>
|
||||
if (commits.find { c =>
|
||||
if (commits.exists { c =>
|
||||
c.id == pull.commitIdTo
|
||||
}.isDefined) {
|
||||
}) {
|
||||
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings)
|
||||
@@ -469,7 +465,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
Database() withTransaction { implicit session =>
|
||||
try {
|
||||
commands.asScala.headOption.foreach { command =>
|
||||
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||
val refName = command.getRefName.split("/")
|
||||
val commitIds = if (refName(1) == "tags") {
|
||||
None
|
||||
|
||||
@@ -2,7 +2,6 @@ package gitbucket.core.servlet
|
||||
|
||||
import java.io.{File, FileOutputStream}
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import gitbucket.core.GitBucketCoreModule
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import javax.servlet._
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
|
||||
@@ -4,7 +4,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService}
|
||||
import gitbucket.core.servlet.{CommitLogHook, Database}
|
||||
import gitbucket.core.util.{SyntaxSugars, Directory}
|
||||
import gitbucket.core.util.Directory
|
||||
import org.apache.sshd.server.{Environment, ExitCallback, SessionAware}
|
||||
import org.apache.sshd.server.command.{Command, CommandFactory}
|
||||
import org.apache.sshd.server.session.ServerSession
|
||||
@@ -144,11 +144,9 @@ class DefaultGitUploadPack(owner: String, repoName: String)
|
||||
|
||||
override protected def runTask(authType: AuthType): Unit = {
|
||||
val execute = Database() withSession { implicit session =>
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""))
|
||||
.map { repositoryInfo =>
|
||||
!repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo)
|
||||
}
|
||||
.getOrElse(false)
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
|
||||
!repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if (execute) {
|
||||
@@ -169,11 +167,9 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss
|
||||
|
||||
override protected def runTask(authType: AuthType): Unit = {
|
||||
val execute = Database() withSession { implicit session =>
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""))
|
||||
.map { repositoryInfo =>
|
||||
isWritableUser(authType, repositoryInfo)
|
||||
}
|
||||
.getOrElse(false)
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
|
||||
isWritableUser(authType, repositoryInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if (execute) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.model.Role
|
||||
import RepositoryService.RepositoryInfo
|
||||
import Implicits._
|
||||
import SyntaxSugars._
|
||||
|
||||
/**
|
||||
* Allows only oneself and administrators.
|
||||
@@ -39,7 +38,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
|
||||
case Some(x) if (repository.owner == x.userName) => action(repository)
|
||||
// TODO Repository management is allowed for only group managers?
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists { m =>
|
||||
m.userName == x.userName && m.isManager == true
|
||||
m.userName == x.userName && m.isManager
|
||||
}) =>
|
||||
action(repository)
|
||||
case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
|
||||
@@ -113,13 +112,10 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService w
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action(repository)
|
||||
case Some(x) if (!repository.repository.isPrivate) => action(repository)
|
||||
case Some(x) if (userName == x.userName) => action(repository)
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x) if (getCollaboratorUserNames(userName, repoName).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
if (isReadable(repository.repository, context.loginAccount) || !repository.repository.isPrivate) {
|
||||
action(repository)
|
||||
} else {
|
||||
Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
object ConfigUtil {
|
||||
@@ -30,12 +28,12 @@ object ConfigUtil {
|
||||
}
|
||||
}
|
||||
|
||||
def convertType[A: ClassTag](value: String): A =
|
||||
defining(implicitly[ClassTag[A]].runtimeClass) { c =>
|
||||
if (c == classOf[Boolean]) value.toBoolean
|
||||
else if (c == classOf[Long]) value.toLong
|
||||
else if (c == classOf[Int]) value.toInt
|
||||
else value
|
||||
}.asInstanceOf[A]
|
||||
def convertType[A: ClassTag](value: String): A = {
|
||||
val c = implicitly[ClassTag[A]].runtimeClass
|
||||
if (c == classOf[Boolean]) value.toBoolean
|
||||
else if (c == classOf[Long]) value.toLong
|
||||
else if (c == classOf[Int]) value.toInt
|
||||
else value
|
||||
}.asInstanceOf[A]
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ object EditorConfigUtil {
|
||||
}
|
||||
|
||||
private def getRevTree: RevTree = {
|
||||
Using.resource(repo.newObjectReader()) { reader: ObjectReader =>
|
||||
Using.resource(repo.newObjectReader()) { (reader: ObjectReader) =>
|
||||
val revWalk = new RevWalk(reader)
|
||||
val id = repo.resolve(revStr)
|
||||
val commit = revWalk.parseCommit(id)
|
||||
@@ -37,7 +37,7 @@ object EditorConfigUtil {
|
||||
}
|
||||
|
||||
override def exists(): Boolean = {
|
||||
Using.resource(repo.newObjectReader()) { reader: ObjectReader =>
|
||||
Using.resource(repo.newObjectReader()) { (reader: ObjectReader) =>
|
||||
try {
|
||||
val treeWalk = Option(TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree))
|
||||
treeWalk.isDefined
|
||||
@@ -52,7 +52,7 @@ object EditorConfigUtil {
|
||||
}
|
||||
|
||||
override def getParent: ResourcePath = {
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull
|
||||
}
|
||||
|
||||
override def openRandomReader(): Resource.RandomReader = {
|
||||
@@ -60,7 +60,7 @@ object EditorConfigUtil {
|
||||
}
|
||||
|
||||
override def openReader(): Reader = {
|
||||
Using.resource(repo.newObjectReader) { reader: ObjectReader =>
|
||||
Using.resource(repo.newObjectReader) { (reader: ObjectReader) =>
|
||||
val treeWalk = TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree)
|
||||
new InputStreamReader(reader.open(treeWalk.getObjectId(0)).openStream, StandardCharsets.UTF_8)
|
||||
}
|
||||
@@ -70,7 +70,7 @@ object EditorConfigUtil {
|
||||
private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath {
|
||||
|
||||
override def getParent: ResourcePath = {
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
|
||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull
|
||||
}
|
||||
|
||||
override def getPath: Ec4jPath = {
|
||||
|
||||
@@ -3,36 +3,39 @@ package gitbucket.core.util
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.tika.Tika
|
||||
import java.io.File
|
||||
import SyntaxSugars._
|
||||
import scala.util.Random
|
||||
|
||||
object FileUtil {
|
||||
|
||||
def getMimeType(name: String): String =
|
||||
defining(new Tika()) { tika =>
|
||||
tika.detect(name) match {
|
||||
case null => "application/octet-stream"
|
||||
case mimeType => mimeType
|
||||
}
|
||||
def getMimeType(name: String): String = {
|
||||
val tika = new Tika()
|
||||
tika.detect(name) match {
|
||||
case null => "application/octet-stream"
|
||||
case mimeType => mimeType
|
||||
}
|
||||
}
|
||||
|
||||
def getMimeType(name: String, bytes: Array[Byte]): String = {
|
||||
defining(getMimeType(name)) { mimeType =>
|
||||
if (mimeType == "application/octet-stream" && isText(bytes)) {
|
||||
"text/plain"
|
||||
} else {
|
||||
mimeType
|
||||
}
|
||||
val mimeType = getMimeType(name)
|
||||
if (mimeType == "application/octet-stream" && isText(bytes)) {
|
||||
"text/plain"
|
||||
} else {
|
||||
mimeType
|
||||
}
|
||||
}
|
||||
|
||||
def getSafeMimeType(name: String): String = {
|
||||
getMimeType(name)
|
||||
def getSafeMimeType(name: String, safeMode: Boolean = true): String = {
|
||||
val mimeType = getMimeType(name)
|
||||
.replace("text/html", "text/plain")
|
||||
.replace("image/svg+xml", "text/plain; charset=UTF-8")
|
||||
|
||||
if (safeMode) {
|
||||
mimeType.replace("image/svg+xml", "text/plain; charset=UTF-8")
|
||||
} else {
|
||||
mimeType
|
||||
}
|
||||
}
|
||||
|
||||
def isImage(name: String): Boolean = getSafeMimeType(name).startsWith("image/")
|
||||
def isImage(name: String, safeMode: Boolean = true): Boolean = getSafeMimeType(name, safeMode).startsWith("image/")
|
||||
|
||||
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
||||
|
||||
@@ -92,4 +95,14 @@ object FileUtil {
|
||||
name
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete given folder recursively.
|
||||
*/
|
||||
def deleteRecursively(f: File): Boolean = {
|
||||
if (f.isDirectory) f.listFiles match {
|
||||
case files: Array[File] => files.foreach(deleteRecursively)
|
||||
case null =>
|
||||
}
|
||||
f.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.net.{InetAddress, URL}
|
||||
import java.net.InetAddress
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import org.apache.commons.net.util.SubnetUtils
|
||||
|
||||
@@ -83,7 +83,9 @@ object JDBCUtil {
|
||||
}
|
||||
if (c == ';' && !stringLiteral) {
|
||||
val sql = new String(out.toByteArray, "UTF-8")
|
||||
conn.update(sql.trim)
|
||||
if (sql != null && !sql.isEmpty()) {
|
||||
conn.update(sql.trim)
|
||||
}
|
||||
out = new ByteArrayOutputStream()
|
||||
} else {
|
||||
out.write(c)
|
||||
@@ -225,7 +227,7 @@ object JDBCUtil {
|
||||
if (noPreds.isEmpty) {
|
||||
if (hasPreds.isEmpty) done else sys.error(hasPreds.toString)
|
||||
} else {
|
||||
val found = noPreds.map { _._1 }
|
||||
val found = noPreds.keys
|
||||
tsort(hasPreds.map { case (k, v) => (k, v -- found) }, done ++ found)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import gitbucket.core.service.RepositoryService
|
||||
import org.eclipse.jgit.api.Git
|
||||
import Directory._
|
||||
import StringUtil._
|
||||
import SyntaxSugars._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.jdk.CollectionConverters._
|
||||
@@ -38,9 +37,8 @@ object JGitUtil {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(JGitUtil.getClass)
|
||||
|
||||
implicit val objectDatabaseReleasable = new Releasable[ObjectDatabase] {
|
||||
override def release(resource: ObjectDatabase): Unit = resource.close()
|
||||
}
|
||||
implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
|
||||
_.close()
|
||||
|
||||
/**
|
||||
* The repository data.
|
||||
@@ -183,7 +181,8 @@ object JGitUtil {
|
||||
|
||||
val summary = getSummaryMessage(fullMessage, shortMessage)
|
||||
|
||||
val description = defining(fullMessage.trim.indexOf('\n')) { i =>
|
||||
val description = {
|
||||
val i = fullMessage.trim.indexOf('\n')
|
||||
if (i >= 0) {
|
||||
Some(fullMessage.trim.substring(i).trim)
|
||||
} else None
|
||||
@@ -383,7 +382,7 @@ object JGitUtil {
|
||||
path: String = ".",
|
||||
baseUrl: Option[String] = None,
|
||||
commitCount: Int = 0,
|
||||
maxFiles: Int = 100
|
||||
maxFiles: Int = 5
|
||||
): List[FileInfo] = {
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
@@ -513,12 +512,10 @@ object JGitUtil {
|
||||
/**
|
||||
* Returns the first line of the commit message.
|
||||
*/
|
||||
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
||||
defining(fullMessage.trim.indexOf('\n')) { i =>
|
||||
defining(if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage) { firstLine =>
|
||||
if (firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||
}
|
||||
}
|
||||
def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
||||
val i = fullMessage.trim.indexOf('\n')
|
||||
val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage
|
||||
if (firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -592,16 +589,15 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
defining(git.getRepository.resolve(revision)) { objectId =>
|
||||
if (objectId == null) {
|
||||
Left(s"${revision} can't be resolved.")
|
||||
} else {
|
||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||
if (path.nonEmpty) {
|
||||
revWalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF))
|
||||
}
|
||||
Right(getCommitLog(revWalk.iterator, 0, Nil))
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if (objectId == null) {
|
||||
Left(s"${revision} can't be resolved.")
|
||||
} else {
|
||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||
if (path.nonEmpty) {
|
||||
revWalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF))
|
||||
}
|
||||
Right(getCommitLog(revWalk.iterator, 0, Nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -662,9 +658,13 @@ object JGitUtil {
|
||||
*/
|
||||
def getLatestCommitFromPaths(git: Git, paths: List[String], revision: String): Map[String, RevCommit] = {
|
||||
val start = getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||
paths.map { path =>
|
||||
paths.flatMap { path =>
|
||||
val commit = git.log.add(start).addPath(path).setMaxCount(1).call.iterator.next
|
||||
(path, commit)
|
||||
if (commit == null) {
|
||||
None
|
||||
} else {
|
||||
Some((path, commit))
|
||||
}
|
||||
}.toMap
|
||||
}
|
||||
|
||||
@@ -675,11 +675,10 @@ object JGitUtil {
|
||||
df.setDiffComparator(RawTextComparator.DEFAULT)
|
||||
df.setDetectRenames(true)
|
||||
getDiffEntries(git, from, to)
|
||||
.map { entry =>
|
||||
.foreach { entry =>
|
||||
df.format(entry)
|
||||
new String(out.toByteArray, "UTF-8")
|
||||
}
|
||||
.mkString("\n")
|
||||
new String(out.toByteArray, "UTF-8")
|
||||
}
|
||||
|
||||
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
||||
@@ -804,22 +803,21 @@ object JGitUtil {
|
||||
*/
|
||||
def getBranchesOfCommit(git: Git, commitId: String): List[String] =
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
||||
git.getRepository.getRefDatabase
|
||||
.getRefsByPrefix(Constants.R_HEADS)
|
||||
.asScala
|
||||
.filter { e =>
|
||||
(revWalk.isMergedInto(
|
||||
commit,
|
||||
revWalk.parseCommit(e.getObjectId)
|
||||
))
|
||||
}
|
||||
.map { e =>
|
||||
e.getName.substring(Constants.R_HEADS.length)
|
||||
}
|
||||
.toList
|
||||
.sorted
|
||||
}
|
||||
val commit = revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
||||
git.getRepository.getRefDatabase
|
||||
.getRefsByPrefix(Constants.R_HEADS)
|
||||
.asScala
|
||||
.filter { e =>
|
||||
(revWalk.isMergedInto(
|
||||
commit,
|
||||
revWalk.parseCommit(e.getObjectId)
|
||||
))
|
||||
}
|
||||
.map { e =>
|
||||
e.getName.substring(Constants.R_HEADS.length)
|
||||
}
|
||||
.toList
|
||||
.sorted
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -847,23 +845,22 @@ object JGitUtil {
|
||||
*/
|
||||
def getTagsOfCommit(git: Git, commitId: String): List[String] =
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
||||
git.getRepository.getRefDatabase
|
||||
.getRefsByPrefix(Constants.R_TAGS)
|
||||
.asScala
|
||||
.filter { e =>
|
||||
(revWalk.isMergedInto(
|
||||
commit,
|
||||
revWalk.parseCommit(e.getObjectId)
|
||||
))
|
||||
}
|
||||
.map { e =>
|
||||
e.getName.substring(Constants.R_TAGS.length)
|
||||
}
|
||||
.toList
|
||||
.sorted
|
||||
.reverse
|
||||
}
|
||||
val commit = revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
||||
git.getRepository.getRefDatabase
|
||||
.getRefsByPrefix(Constants.R_TAGS)
|
||||
.asScala
|
||||
.filter { e =>
|
||||
(revWalk.isMergedInto(
|
||||
commit,
|
||||
revWalk.parseCommit(e.getObjectId)
|
||||
))
|
||||
}
|
||||
.map { e =>
|
||||
e.getName.substring(Constants.R_TAGS.length)
|
||||
}
|
||||
.toList
|
||||
.sorted
|
||||
.reverse
|
||||
}
|
||||
|
||||
def initRepository(dir: java.io.File): Unit =
|
||||
@@ -879,11 +876,11 @@ object JGitUtil {
|
||||
|
||||
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
||||
|
||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit =
|
||||
defining(repository.getConfig) { config =>
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = {
|
||||
val config = repository.getConfig
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
|
||||
def getDefaultBranch(
|
||||
git: Git,
|
||||
@@ -912,10 +909,10 @@ object JGitUtil {
|
||||
}
|
||||
Right("Tag added.")
|
||||
} catch {
|
||||
case e: ConcurrentRefUpdateException => Left("Sorry, some error occurs.")
|
||||
case e: InvalidTagNameException => Left("Sorry, that name is invalid.")
|
||||
case e: NoHeadException => Left("Sorry, this repo doesn't have HEAD reference")
|
||||
case e: GitAPIException => Left("Sorry, some Git operation error occurs.")
|
||||
case _: ConcurrentRefUpdateException => Left("Sorry, some error occurs.")
|
||||
case _: InvalidTagNameException => Left("Sorry, that name is invalid.")
|
||||
case _: NoHeadException => Left("Sorry, this repo doesn't have HEAD reference")
|
||||
case _: GitAPIException => Left("Sorry, some Git operation error occurs.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -924,7 +921,7 @@ object JGitUtil {
|
||||
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
|
||||
Right("Branch created.")
|
||||
} catch {
|
||||
case e: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
|
||||
case _: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
|
||||
// JGitInternalException occurs when new branch name is 'a' and the branch whose name is 'a/*' exists.
|
||||
case _: InvalidRefNameException | _: JGitInternalException => Left("Sorry, that name is invalid.")
|
||||
}
|
||||
@@ -1052,13 +1049,13 @@ object JGitUtil {
|
||||
!loader.isLarge && new String(loader.getBytes(), "UTF-8").startsWith("version https://git-lfs.github.com/spec/v1")
|
||||
}
|
||||
|
||||
def getContentInfo(git: Git, path: String, objectId: ObjectId): ContentInfo = {
|
||||
def getContentInfo(git: Git, path: String, objectId: ObjectId, safeMode: Boolean): ContentInfo = {
|
||||
// Viewer
|
||||
Using.resource(git.getRepository.getObjectDatabase) { db =>
|
||||
val loader = db.open(objectId)
|
||||
val isLfs = isLfsPointer(loader)
|
||||
val large = FileUtil.isLarge(loader.getSize)
|
||||
val viewer = if (FileUtil.isImage(path)) "image" else if (large) "large" else "other"
|
||||
val viewer = if (FileUtil.isImage(path, safeMode)) "image" else if (large) "large" else "other"
|
||||
val bytes = if (viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None
|
||||
val size = Some(getContentSize(loader))
|
||||
|
||||
@@ -1101,7 +1098,7 @@ object JGitUtil {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: MissingObjectException => None
|
||||
case _: MissingObjectException => None
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1118,7 +1115,7 @@ object JGitUtil {
|
||||
Some(f(db.open(id)))
|
||||
}
|
||||
} catch {
|
||||
case e: MissingObjectException => None
|
||||
case _: MissingObjectException => None
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1162,12 +1159,12 @@ object JGitUtil {
|
||||
requestUserName: String,
|
||||
requestRepositoryName: String,
|
||||
requestBranch: String
|
||||
): String =
|
||||
defining(getAllCommitIds(oldGit)) { existIds =>
|
||||
getCommitLogs(newGit, requestBranch, true) { commit =>
|
||||
existIds.contains(commit.name) && getBranchesOfCommit(oldGit, commit.getName).contains(branch)
|
||||
}.head.id
|
||||
}
|
||||
): String = {
|
||||
val existIds = getAllCommitIds(oldGit)
|
||||
getCommitLogs(newGit, requestBranch, true) { commit =>
|
||||
existIds.contains(commit.name) && getBranchesOfCommit(oldGit, commit.getName).contains(branch)
|
||||
}.head.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and return (commitIdTo, commitIdFrom)
|
||||
@@ -1264,7 +1261,7 @@ object JGitUtil {
|
||||
val blame = blamer.call()
|
||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||
var idLine = List[(String, Int)]()
|
||||
0.to(blame.getResultContents().size() - 1).map { i =>
|
||||
0.until(blame.getResultContents().size()).foreach { i =>
|
||||
val c = blame.getSourceCommit(i)
|
||||
if (!blameMap.contains(c.name)) {
|
||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.model.Account
|
||||
import SyntaxSugars._
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.Ldap
|
||||
import com.novell.ldap._
|
||||
@@ -246,20 +245,18 @@ object LDAPUtil {
|
||||
userNameAttribute: String,
|
||||
userName: String,
|
||||
mailAttribute: String
|
||||
): Option[String] =
|
||||
defining(
|
||||
conn.search(
|
||||
userDN,
|
||||
LDAPConnection.SCOPE_BASE,
|
||||
userNameAttribute + "=" + userName,
|
||||
Array[String](mailAttribute),
|
||||
false
|
||||
)
|
||||
) { results =>
|
||||
if (results.hasMore) {
|
||||
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
||||
} else None
|
||||
}
|
||||
): Option[String] = {
|
||||
val results = conn.search(
|
||||
userDN,
|
||||
LDAPConnection.SCOPE_BASE,
|
||||
userNameAttribute + "=" + userName,
|
||||
Array[String](mailAttribute),
|
||||
false
|
||||
)
|
||||
if (results.hasMore) {
|
||||
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
||||
} else None
|
||||
}
|
||||
|
||||
private def findFullName(
|
||||
conn: LDAPConnection,
|
||||
@@ -267,20 +264,18 @@ object LDAPUtil {
|
||||
userNameAttribute: String,
|
||||
userName: String,
|
||||
nameAttribute: String
|
||||
): Option[String] =
|
||||
defining(
|
||||
conn.search(
|
||||
userDN,
|
||||
LDAPConnection.SCOPE_BASE,
|
||||
userNameAttribute + "=" + userName,
|
||||
Array[String](nameAttribute),
|
||||
false
|
||||
)
|
||||
) { results =>
|
||||
if (results.hasMore) {
|
||||
Option(results.next.getAttribute(nameAttribute)).map(_.getStringValue)
|
||||
} else None
|
||||
}
|
||||
): Option[String] = {
|
||||
val results = conn.search(
|
||||
userDN,
|
||||
LDAPConnection.SCOPE_BASE,
|
||||
userNameAttribute + "=" + userName,
|
||||
Array[String](nameAttribute),
|
||||
false
|
||||
)
|
||||
if (results.hasMore) {
|
||||
Option(results.next.getAttribute(nameAttribute)).map(_.getStringValue)
|
||||
} else None
|
||||
}
|
||||
|
||||
case class LDAPUserInfo(userName: String, fullName: String, mailAddress: String)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package gitbucket.core.util
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.locks.{ReentrantLock, Lock}
|
||||
import SyntaxSugars._
|
||||
|
||||
object LockUtil {
|
||||
|
||||
@@ -24,7 +23,8 @@ object LockUtil {
|
||||
/**
|
||||
* Synchronizes a given function which modifies the working copy of the wiki repository.
|
||||
*/
|
||||
def lock[T](key: String)(f: => T): T = defining(getLockObject(key)) { lock =>
|
||||
def lock[T](key: String)(f: => T): T = {
|
||||
val lock = getLockObject(key)
|
||||
try {
|
||||
lock.lock()
|
||||
f
|
||||
|
||||
@@ -41,7 +41,7 @@ class Mailer(settings: SystemSettings) {
|
||||
htmlMsg: Option[String] = None,
|
||||
loginAccount: Option[Account] = None
|
||||
): Option[HtmlEmail] = {
|
||||
if (settings.notification == true) {
|
||||
if (settings.notification) {
|
||||
settings.smtp.map { smtp =>
|
||||
val email = new HtmlEmail
|
||||
email.setHostName(smtp.host)
|
||||
@@ -51,7 +51,7 @@ class Mailer(settings: SystemSettings) {
|
||||
}
|
||||
smtp.ssl.foreach { ssl =>
|
||||
email.setSSLOnConnect(ssl)
|
||||
if (ssl == true) {
|
||||
if (ssl) {
|
||||
email.setSslSmtpPort(smtp.port.get.toString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.security.SecureRandom
|
||||
import java.util.{Base64, UUID}
|
||||
|
||||
import org.mozilla.universalchardet.UniversalDetector
|
||||
import SyntaxSugars._
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import org.apache.commons.io.input.BOMInputStream
|
||||
@@ -45,11 +44,11 @@ object StringUtil {
|
||||
s"""$$pbkdf2-sha256$$${iter}$$${base64Encode(salt)}$$${base64Encode(s.getEncoded)}"""
|
||||
}
|
||||
|
||||
def sha1(value: String): String =
|
||||
defining(java.security.MessageDigest.getInstance("SHA-1")) { md =>
|
||||
md.update(value.getBytes)
|
||||
md.digest.map(b => "%02x".format(b)).mkString
|
||||
}
|
||||
def sha1(value: String): String = {
|
||||
val md = java.security.MessageDigest.getInstance("SHA-1")
|
||||
md.update(value.getBytes)
|
||||
md.digest.map(b => "%02x".format(b)).mkString
|
||||
}
|
||||
|
||||
def md5(value: String): String = {
|
||||
val md = java.security.MessageDigest.getInstance("MD5")
|
||||
@@ -89,15 +88,15 @@ object StringUtil {
|
||||
def convertFromByteArray(content: Array[Byte]): String =
|
||||
IOUtils.toString(new BOMInputStream(new java.io.ByteArrayInputStream(content)), detectEncoding(content))
|
||||
|
||||
def detectEncoding(content: Array[Byte]): String =
|
||||
defining(new UniversalDetector(null)) { detector =>
|
||||
detector.handleData(content, 0, content.length)
|
||||
detector.dataEnd()
|
||||
detector.getDetectedCharset match {
|
||||
case null => "UTF-8"
|
||||
case e => e
|
||||
}
|
||||
def detectEncoding(content: Array[Byte]): String = {
|
||||
val detector = new UniversalDetector(null)
|
||||
detector.handleData(content, 0, content.length)
|
||||
detector.dataEnd()
|
||||
detector.getDetectedCharset match {
|
||||
case null => "UTF-8"
|
||||
case e => e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts line separator in the given content.
|
||||
@@ -144,6 +143,19 @@ object StringUtil {
|
||||
.toSeq
|
||||
.distinct
|
||||
|
||||
/**
|
||||
* Extract issue id like ```owner/repository#issueId``` from the given message.
|
||||
*
|
||||
*@param message the message which may contains issue id
|
||||
* @return the iterator of issue id
|
||||
*/
|
||||
def extractGlobalIssueId(message: String): List[(Option[String], Option[String], Option[String])] =
|
||||
"\\s?([\\w-\\.]+)?\\/?([\\w\\-\\.]+)?#(\\d+)\\s?".r
|
||||
.findAllIn(message)
|
||||
.matchData
|
||||
.map(i => (Option(i.group(1)), Option(i.group(2)), Option(i.group(3))))
|
||||
.toList
|
||||
|
||||
/**
|
||||
* Extract close issue id like ```close #issueId ``` from the given message.
|
||||
*
|
||||
@@ -172,8 +184,7 @@ object StringUtil {
|
||||
def removeUserName(baseUrl: String): String = baseUrl.replaceFirst("(https?://).+@", "$1")
|
||||
|
||||
gitRepositoryUrl match {
|
||||
case GitBucketUrlPattern(base, user, repository)
|
||||
if baseUrl.map(removeUserName(base).startsWith).getOrElse(false) =>
|
||||
case GitBucketUrlPattern(base, user, repository) if baseUrl.exists(removeUserName(base).startsWith) =>
|
||||
s"${removeUserName(base)}/$user/$repository"
|
||||
case GitHubUrlPattern(_, user, repository) => s"https://github.com/$user/$repository"
|
||||
case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository"
|
||||
|
||||
@@ -1,56 +1,14 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import scala.util.control.Exception._
|
||||
import scala.language.reflectiveCalls
|
||||
|
||||
/**
|
||||
* Provides control facilities.
|
||||
*/
|
||||
object SyntaxSugars {
|
||||
|
||||
@deprecated("Use scala.util.Try instead", "4.36.0")
|
||||
def defining[A, B](value: A)(f: A => B): B = f(value)
|
||||
|
||||
@deprecated("Use scala.util.Using.resource instead", "4.32.0")
|
||||
def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B =
|
||||
try f(resource)
|
||||
finally {
|
||||
if (resource != null) {
|
||||
ignoring(classOf[Throwable]) {
|
||||
resource.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@deprecated("Use scala.util.Using.resources instead", "4.32.0")
|
||||
def using[A <: { def close(): Unit }, B <: { def close(): Unit }, C](resource1: A, resource2: B)(f: (A, B) => C): C =
|
||||
try f(resource1, resource2)
|
||||
finally {
|
||||
if (resource1 != null) {
|
||||
ignoring(classOf[Throwable]) {
|
||||
resource1.close()
|
||||
}
|
||||
}
|
||||
if (resource2 != null) {
|
||||
ignoring(classOf[Throwable]) {
|
||||
resource2.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@deprecated("Use scala.util.Using.resource instead", "4.32.0")
|
||||
def using[T](git: Git)(f: Git => T): T =
|
||||
try f(git)
|
||||
finally git.getRepository.close()
|
||||
|
||||
@deprecated("Use scala.util.Using.resources instead", "4.32.0")
|
||||
def using[T](git1: Git, git2: Git)(f: (Git, Git) => T): T =
|
||||
try f(git1, git2)
|
||||
finally {
|
||||
git1.getRepository.close()
|
||||
git2.getRepository.close()
|
||||
}
|
||||
|
||||
@deprecated("Use scala.util.Try instead", "4.36.0")
|
||||
def ignore[T](f: => Unit): Unit =
|
||||
try {
|
||||
f
|
||||
|
||||
@@ -3,7 +3,7 @@ package gitbucket.core.util
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.awt.image.BufferedImage
|
||||
import javax.imageio.ImageIO
|
||||
import java.awt.{Color, Font, Graphics2D, RenderingHints}
|
||||
import java.awt.{Color, Font, RenderingHints}
|
||||
import java.awt.font.{FontRenderContext, TextLayout}
|
||||
import java.awt.geom.AffineTransform
|
||||
|
||||
|
||||
@@ -45,11 +45,14 @@ trait AvatarImageProvider { self: RequestCache =>
|
||||
|
||||
if (tooltip) {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" data-toggle="tooltip" title="${userName}" alt="@${userName}" />"""
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}"
|
||||
| data-toggle="tooltip" title="${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
)
|
||||
} else {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;" alt="@${userName}" />"""
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" } else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,21 +10,33 @@ trait LinkConverter { self: RequestCache =>
|
||||
/**
|
||||
* Creates a link to the issue or the pull request from the issue id.
|
||||
*/
|
||||
protected def createIssueLink(repository: RepositoryService.RepositoryInfo, issueId: Int, title: String)(
|
||||
protected def createIssueLink(owner: String, repository: String, issueId: Int, title: String)(
|
||||
implicit context: Context
|
||||
): String = {
|
||||
val userName = repository.repository.userName
|
||||
val repositoryName = repository.repository.repositoryName
|
||||
|
||||
getIssueFromCache(userName, repositoryName, issueId.toString) match {
|
||||
getIssueFromCache(owner, repository, issueId.toString) match {
|
||||
case Some(issue) =>
|
||||
s"""<a href="${context.path}/${userName}/${repositoryName}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil
|
||||
s"""<a href="${context.path}/${owner}/${repository}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil
|
||||
.escapeHtml(title)}</strong> #${issueId}</a>"""
|
||||
case None =>
|
||||
s"Unknown #${issueId}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a global link to the issue or the pull request from the issue id.
|
||||
*/
|
||||
protected def createGlobalIssueLink(owner: String, repository: String, issueId: Int, title: String)(
|
||||
implicit context: Context
|
||||
): String = {
|
||||
getIssueFromCache(owner, repository, issueId.toString) match {
|
||||
case Some(issue) =>
|
||||
s"""<a href="${context.path}/${owner}/${repository}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil
|
||||
.escapeHtml(title)}</strong> ${owner}/${repository}#${issueId}</a>"""
|
||||
case None =>
|
||||
s"Unknown ${owner}/${repository}#${issueId}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts issue id, username and commit id to link in the given text.
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,8 @@ import gitbucket.core.plugin.{PluginRegistry, RenderRequest}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.{RepositoryService, RequestCache}
|
||||
import gitbucket.core.util.{FileUtil, JGitUtil, StringUtil}
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import org.json4s.Formats
|
||||
import play.twirl.api.{Html, HtmlFormat}
|
||||
|
||||
/**
|
||||
@@ -158,10 +160,19 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
/**
|
||||
* Creates a link to the issue or the pull request from the issue id.
|
||||
*/
|
||||
def issueLink(repository: RepositoryService.RepositoryInfo, issueId: Int, title: String)(
|
||||
def issueLink(owner: String, repository: String, issueId: Int, title: String)(
|
||||
implicit context: Context
|
||||
): Html = {
|
||||
Html(createIssueLink(repository, issueId, title))
|
||||
Html(createIssueLink(owner, repository, issueId, title))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a global link to the issue or the pull request from the issue id.
|
||||
*/
|
||||
def issueGlobalLink(owner: String, repository: String, issueId: Int, title: String)(
|
||||
implicit context: Context
|
||||
): Html = {
|
||||
Html(createGlobalIssueLink(owner, repository, issueId, title))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,7 +336,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
)(implicit context: Context): Html = {
|
||||
|
||||
val avatarHtml = avatar(userName, size, tooltip, mailAddress)
|
||||
val contentHtml = if (label == true) Html(avatarHtml.body + " " + userName) else avatarHtml
|
||||
val contentHtml = if (label) Html(avatarHtml.body + " " + userName) else avatarHtml
|
||||
|
||||
userWithContent(userName, mailAddress)(contentHtml)
|
||||
}
|
||||
@@ -386,7 +397,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* Render a given object as the JSON string.
|
||||
*/
|
||||
def json(obj: AnyRef): String = {
|
||||
implicit val formats = org.json4s.DefaultFormats
|
||||
implicit val formats: Formats = org.json4s.DefaultFormats
|
||||
org.json4s.jackson.Serialization.write(obj)
|
||||
}
|
||||
|
||||
@@ -435,14 +446,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
}
|
||||
result.append(c)
|
||||
}
|
||||
case '>' if tag == true => {
|
||||
case '>' if tag => {
|
||||
tag = false
|
||||
result.append(c)
|
||||
}
|
||||
case _ if tag == false => {
|
||||
text.append(c)
|
||||
}
|
||||
case _ if tag == true => {
|
||||
case _ if tag => {
|
||||
result.append(c)
|
||||
}
|
||||
}
|
||||
@@ -471,7 +482,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
def diff(jsonString: String): Html = {
|
||||
import org.json4s._
|
||||
import org.json4s.jackson.JsonMethods._
|
||||
implicit val formats = DefaultFormats
|
||||
implicit val formats: Formats = DefaultFormats
|
||||
|
||||
val diff = parse(jsonString).extract[Seq[CommentDiffLine]]
|
||||
|
||||
@@ -506,4 +517,6 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
s"$baseUrl${if (baseUrl.contains("?")) "&" else "?"}$queryString"
|
||||
}
|
||||
|
||||
def md5(value: String): String = DigestUtils.md5Hex(value)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
@(account: gitbucket.core.model.Account,
|
||||
personalTokens: List[gitbucket.core.model.AccessToken],
|
||||
gneratedToken: Option[(gitbucket.core.model.AccessToken, String)])(implicit context: gitbucket.core.controller.Context)
|
||||
generatedToken: Option[(gitbucket.core.model.AccessToken, String)])(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Applications"){
|
||||
@gitbucket.core.account.html.menu("application", context.loginAccount.get.userName, false){
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Personal access tokens</div>
|
||||
<div class="panel-body">
|
||||
@if(personalTokens.isEmpty && gneratedToken.isEmpty){
|
||||
@if(personalTokens.isEmpty && generatedToken.isEmpty){
|
||||
No tokens.
|
||||
} else {
|
||||
Tokens you have generated which can be used to access the GitBucket API.
|
||||
<hr style="margin-top: 10px;">
|
||||
}
|
||||
@gneratedToken.map { case (token, tokenString) =>
|
||||
@generatedToken.map { case (token, tokenString) =>
|
||||
<div class="alert alert-info">
|
||||
Make sure to copy your new personal access token now. You won't be able to see it again!
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,8 @@ $(function(){
|
||||
addExtraMailAddress();
|
||||
$('#extraMailAddresses').on('change', '.extraMailAddress', checkExtraMailAddress);
|
||||
$('#save').click(function(){
|
||||
if($('#password').val() != ''){
|
||||
let pwval = $('#password').val();
|
||||
if(typeof(pwval) != 'undefined' && pwval != ''){
|
||||
return confirm('Are you sure you want to change password?');
|
||||
} else {
|
||||
return true;
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
case "delete_wiki" => simpleActivity(activity)
|
||||
})
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -4,58 +4,119 @@
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.helper.html.dropdown(
|
||||
value = if(branch.length == 40) branch.substring(0, 10) else branch,
|
||||
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
|
||||
prefix = if(repository.branchList.contains(branch)) "branch" else if (repository.tags.map(_.name).contains(branch)) "tag" else "tree",
|
||||
style = "min-width: 200px;",
|
||||
maxValueWidth = "200px"
|
||||
) {
|
||||
<li><div id="branch-control-title">Switch branches<button id="branch-control-close" class="pull-right">×</button></div></li>
|
||||
<li><input id="branch-control-input" type="text" class="form-control input-sm dropdown-filter-input" placeholder="Find or create branch ..."/></li>
|
||||
@body
|
||||
@if(hasWritePermission) {
|
||||
<li id="create-branch" style="display: none;">
|
||||
<a><form action="@helpers.url(repository)/branches" method="post" style="margin: 0;">
|
||||
<span class="strong">Create branch: <span class="new-branch"></span></span>
|
||||
<br><span style="padding-left: 17px;">from '@branch'</span>
|
||||
<input type="hidden" name="new">
|
||||
<input type="hidden" name="from" value="@branch">
|
||||
</form></a>
|
||||
<li>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active" id="branch-control-tab-branches"><a href="javascript:void(0);" class="nav-item" id="nav-item-branches">Branches</a></li>
|
||||
<li id="branch-control-tab-tags"><a href="javascript:void(0);" class="nav-item" id="nav-item-tags">Tags</a></li>
|
||||
<li><button id="branch-control-close" class="pull-right">×</button></li>
|
||||
</ul>
|
||||
<li>
|
||||
<input id="branch-control-input" type="text" class="form-control input-sm dropdown-filter-input"/>
|
||||
</li>
|
||||
}
|
||||
@body
|
||||
@if(hasWritePermission) {
|
||||
<li id="create-branch" style="display: none;">
|
||||
<a><form action="@helpers.url(repository)/branches" method="post" style="margin: 0;">
|
||||
<span class="strong">Create branch: <span class="new-branch"></span></span>
|
||||
<br><span style="padding-left: 17px;">from '@branch'</span>
|
||||
<input type="hidden" name="new">
|
||||
<input type="hidden" name="from" value="@branch">
|
||||
</form></a>
|
||||
</li>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#branch-control-input').parent().click(function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$('#branch-control-close').click(function() {
|
||||
$('[data-toggle="dropdown"]').parent().removeClass('open');
|
||||
});
|
||||
|
||||
$('#branch-control-input').keyup(function() {
|
||||
var inputVal = $('#branch-control-input').val();
|
||||
$.each($('#branch-control-input').parent().parent().find('a'), function(index, elem) {
|
||||
if (!inputVal || !elem.text.trim() || elem.text.trim().toLowerCase().indexOf(inputVal.toLowerCase()) >= 0) {
|
||||
$(elem).parent().show();
|
||||
} else {
|
||||
$(elem).parent().hide();
|
||||
}
|
||||
});
|
||||
@if(hasWritePermission) {
|
||||
if (inputVal) {
|
||||
$('#create-branch').parent().find('li:last-child').show().find('.new-branch').text(inputVal);
|
||||
} else {
|
||||
$('#create-branch').parent().find('li:last-child').hide();
|
||||
}
|
||||
}
|
||||
updateBranchControlListFilter();
|
||||
});
|
||||
|
||||
@if(hasWritePermission) {
|
||||
$('#create-branch').click(function() {
|
||||
$(this).find('input[name="new"]').val($('.dropdown-menu input').val())
|
||||
$(this).find('form').submit()
|
||||
});
|
||||
}
|
||||
|
||||
$('.btn-group').click(function() {
|
||||
$('#branch-control-input').val('');
|
||||
$('.dropdown-menu li').show();
|
||||
//$('.dropdown-menu li').show();
|
||||
$('#create-branch').hide();
|
||||
});
|
||||
|
||||
$('#nav-item-branches').click(function(e) {
|
||||
e.stopPropagation();
|
||||
updateBranchControlList('branches');
|
||||
});
|
||||
|
||||
$('#nav-item-tags').click(function(e) {
|
||||
e.stopPropagation();
|
||||
updateBranchControlList('tags');
|
||||
});
|
||||
|
||||
function updateBranchControlList(active) {
|
||||
if (active == 'branches') {
|
||||
$('li#branch-control-tab-branches').addClass('active');
|
||||
$('li#branch-control-tab-tags').removeClass('active');
|
||||
|
||||
$('li.branch-control-item-branch').show();
|
||||
$('li.branch-control-item-branch > a').addClass('active');
|
||||
|
||||
$('li.branch-control-item-tag').hide();
|
||||
$('li.branch-control-item-tag > a').removeClass('active');
|
||||
@if(hasWritePermission) {
|
||||
$('#branch-control-input').attr('placeholder', 'Find or create branch ...');
|
||||
} else {
|
||||
$('#branch-control-input').attr('placeholder', 'Find branch ...');
|
||||
}
|
||||
} else if (active == 'tags') {
|
||||
$('li#branch-control-tab-branches').removeClass('active');
|
||||
$('li#branch-control-tab-tags').addClass('active');
|
||||
|
||||
$('li.branch-control-item-branch').hide();
|
||||
$('li.branch-control-item-branch > a').removeClass('active');
|
||||
|
||||
$('li.branch-control-item-tag').show();
|
||||
$('li.branch-control-item-tag > a').addClass('active');
|
||||
$('#branch-control-input').attr('placeholder', 'Find tag ...');
|
||||
}
|
||||
updateBranchControlListFilter();
|
||||
}
|
||||
|
||||
function updateBranchControlListFilter() {
|
||||
const inputVal = $('#branch-control-input').val();
|
||||
$.each($('#branch-control-input').parent().parent().find('a.active').not('.nav-item'), function(index, elem) {
|
||||
if (!inputVal || !elem.text.trim() || elem.text.trim().toLowerCase().indexOf(inputVal.toLowerCase()) >= 0) {
|
||||
$(elem).parent().show();
|
||||
} else {
|
||||
$(elem).parent().hide();
|
||||
}
|
||||
});
|
||||
if ($('li#branch-control-tab-branches.active').length > 0) {
|
||||
@if(hasWritePermission) {
|
||||
if (inputVal) {
|
||||
$('#create-branch').parent().find('li:last-child').show().find('.new-branch').text(inputVal);
|
||||
} else {
|
||||
$('#create-branch').parent().find('li:last-child').hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the branch control list
|
||||
updateBranchControlList('branches');
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
showLineNotes: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
<style>
|
||||
th.line-num {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@if(showIndex){
|
||||
<div class="pull-right" style="margin-bottom: 10px;">
|
||||
@if(oldCommitId.isEmpty && newCommitId.isDefined) {
|
||||
@@ -59,7 +64,7 @@
|
||||
</div>
|
||||
}
|
||||
<span class="diffstat"><i class="octicon octicon-diff-renamed"></i></span>
|
||||
<span class="monospace">@diff.oldPath → @diff.newPath</span>
|
||||
<a href="#diff-@helpers.md5(diff.newPath)" id="diff-@helpers.md5(diff.newPath)" class="file-hash"><span class="strong">@diff.oldPath → @diff.newPath</span></a>
|
||||
}
|
||||
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
|
||||
@if(newCommitId.isDefined){
|
||||
@@ -76,7 +81,7 @@
|
||||
<i class="octicon octicon-diff-modified"></i>
|
||||
}
|
||||
</span>
|
||||
<span class="monospace">@diff.newPath</span>
|
||||
<a href="#diff-@helpers.md5(diff.newPath)" id="diff-@helpers.md5(diff.newPath)" class="file-hash"><span class="strong">@diff.newPath</span></a>
|
||||
}
|
||||
@if(diff.changeType == ChangeType.DELETE){
|
||||
@if(oldCommitId.isDefined){
|
||||
@@ -86,7 +91,7 @@
|
||||
</div>
|
||||
}
|
||||
<span class="diffstat"><i class="octicon octicon-diff-removed"></i></span>
|
||||
<span class="monospace">@diff.oldPath</span>
|
||||
<a href="#diff-@helpers.md5(diff.oldPath)" id="diff-@helpers.md5(diff.oldPath)" class="file-hash"><span class="strong">@diff.oldPath</span></a>
|
||||
}
|
||||
@if(diff.oldMode != diff.newMode){
|
||||
<span class="monospace">@diff.oldMode → @diff.newMode</span>
|
||||
@@ -104,8 +109,8 @@
|
||||
} else {
|
||||
@if(diff.newContent != None || diff.oldContent != None){
|
||||
<div id="diffText-@i" class="diffText"></div>
|
||||
<input type="hidden" id="newText-@i" data-file-name="@diff.oldPath" data-val="@diff.newContent">
|
||||
<input type="hidden" id="oldText-@i" data-file-name="@diff.newPath" data-val="@diff.oldContent">
|
||||
<input type="hidden" id="newText-@i" data-file-name="@diff.newPath" data-val="@diff.newContent">
|
||||
<input type="hidden" id="oldText-@i" data-file-name="@diff.oldPath" data-val="@diff.oldContent">
|
||||
} else {
|
||||
@if(diff.newIsImage || diff.oldIsImage){
|
||||
<div class="diff-image-render diff2up">
|
||||
@@ -180,6 +185,10 @@ $(function(){
|
||||
}
|
||||
renderDiffs();
|
||||
|
||||
window.onhashchange = function(){
|
||||
updateHighlighting();
|
||||
}
|
||||
|
||||
$('.toggle-notes').change(function() {
|
||||
if (!$(this).prop('checked')) {
|
||||
$(this).closest('table').find('.not-diff.inline-comment-form').remove();
|
||||
@@ -188,7 +197,7 @@ $(function(){
|
||||
});
|
||||
|
||||
$('.ignore-whitespace').change(function() {
|
||||
renderOneDiff($(this).closest("table").find(".diffText"), window.viewType);
|
||||
renderOneDiff($(this).closest("table").find(".diffText"), window.viewType, $(this).closest("table").find(".file-hash")[0].id);
|
||||
});
|
||||
|
||||
function getInlineContainer(where) {
|
||||
@@ -208,7 +217,7 @@ $(function(){
|
||||
|
||||
function showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr){
|
||||
// assemble Ajax url
|
||||
var url = '@helpers.url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
|
||||
let url = '@helpers.url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
|
||||
if (!isNaN(oldLineNumber) && oldLineNumber) {
|
||||
url += ('&oldLineNumber=' + oldLineNumber)
|
||||
}
|
||||
@@ -218,7 +227,7 @@ $(function(){
|
||||
// send Ajax request
|
||||
$.get(url, { dataType : 'html' }, function(responseContent) {
|
||||
// create container
|
||||
var tmp;
|
||||
let tmp;
|
||||
if (!isNaN(oldLineNumber) && oldLineNumber) {
|
||||
if (!isNaN(newLineNumber) && newLineNumber) {
|
||||
tmp = getInlineContainer();
|
||||
@@ -240,16 +249,16 @@ $(function(){
|
||||
|
||||
// Add comment button
|
||||
$('.diff-outside').on('click','table.diff .add-comment',function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $check = $this.closest('table:not(.diff)').find('.toggle-notes');
|
||||
const $this = $(this);
|
||||
const $tr = $this.closest('tr');
|
||||
const $check = $this.closest('table:not(.diff)').find('.toggle-notes');
|
||||
if (!$check.prop('checked')) {
|
||||
$check.prop('checked', true).trigger('change');
|
||||
}
|
||||
if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) {
|
||||
var commitId = $this.closest('.table-bordered').attr('commitId'),
|
||||
fileName = $this.closest('.table-bordered').attr('fileName'),
|
||||
oldLineNumber, newLineNumber;
|
||||
const commitId = $this.closest('.table-bordered').attr('commitId'),
|
||||
fileName = $this.closest('.table-bordered').attr('fileName');
|
||||
let oldLineNumber, newLineNumber;
|
||||
if (window.viewType == 0) {
|
||||
oldLineNumber = $this.parent().prev('.oldline').attr('line-number');
|
||||
newLineNumber = $this.parent().prev('.newline').attr('line-number');
|
||||
@@ -268,21 +277,37 @@ $(function(){
|
||||
|
||||
// Reply comment
|
||||
$('.diff-outside').on('click', '.reply-comment',function(){
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var commitId = $this.closest('.table-bordered').attr('commitId');
|
||||
var fileName = $this.data('filename');
|
||||
var oldLineNumber = $this.data('oldline');
|
||||
var newLineNumber = $this.data('newline');
|
||||
const $this = $(this);
|
||||
const $tr = $this.closest('tr');
|
||||
const commitId = $this.closest('.table-bordered').attr('commitId');
|
||||
const fileName = $this.data('filename');
|
||||
const oldLineNumber = $this.data('oldline');
|
||||
const newLineNumber = $this.data('newline');
|
||||
|
||||
showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr);
|
||||
});
|
||||
|
||||
// Line selection
|
||||
$('.diff-outside').on('click','table.diff th.line-num',function(e) {
|
||||
const $this = $(this);
|
||||
const hash = location.hash;
|
||||
const baseUrl = location.toString().split("#")[0];
|
||||
|
||||
if (e.shiftKey == true && hash.match(/#diff-\w+-L\d+(-L\d+)?/)) {
|
||||
const fragments = hash.split('-');
|
||||
window.history.pushState('', '', baseUrl + '#diff-' + fragments[1] + '-' + fragments[2] + '-' + $this[0].id.split('-')[2]);
|
||||
} else {
|
||||
window.history.pushState('', '', baseUrl + '#' + $this[0].id);
|
||||
}
|
||||
getSelection().empty();
|
||||
updateHighlighting();
|
||||
});
|
||||
|
||||
function renderOneCommitCommentIntoDiff($v, diff){
|
||||
//var filename = $v.attr('filename');
|
||||
var oldline = $v.attr('oldline');
|
||||
var newline = $v.attr('newline');
|
||||
var tmp;
|
||||
const oldline = $v.attr('oldline');
|
||||
const newline = $v.attr('newline');
|
||||
let tmp;
|
||||
if (typeof oldline !== 'undefined') {
|
||||
if (typeof newline !== 'undefined') {
|
||||
tmp = getInlineContainer();
|
||||
@@ -312,8 +337,8 @@ $(function(){
|
||||
}
|
||||
del = 5 - add;
|
||||
}
|
||||
var ret = $('<div class="diffstat-bar">');
|
||||
for(var i = 0; i < 5; i++){
|
||||
const ret = $('<div class="diffstat-bar">');
|
||||
for(let i = 0; i < 5; i++){
|
||||
if(add){
|
||||
ret.append('<span class="text-diff-added">■</span>');
|
||||
add--;
|
||||
@@ -327,13 +352,13 @@ $(function(){
|
||||
return ret;
|
||||
}
|
||||
|
||||
function renderOneDiff(diffText, viewType){
|
||||
var table = diffText.closest("table[data-diff-id]");
|
||||
var i = table.data("diff-id");
|
||||
var ignoreWhiteSpace = table.find('.ignore-whitespace').prop('checked');
|
||||
diffUsingJS('oldText-' + i, 'newText-' + i, diffText.attr('id'), viewType, ignoreWhiteSpace);
|
||||
var add = diffText.find("table").attr("add") * 1;
|
||||
var del = diffText.find("table").attr("del") * 1;
|
||||
function renderOneDiff(diffText, viewType, fileHash){
|
||||
const table = diffText.closest("table[data-diff-id]");
|
||||
const i = table.data("diff-id");
|
||||
const ignoreWhiteSpace = table.find('.ignore-whitespace').prop('checked');
|
||||
diffUsingJS('oldText-' + i, 'newText-' + i, diffText.attr('id'), viewType, ignoreWhiteSpace, fileHash);
|
||||
const add = diffText.find("table").attr("add") * 1;
|
||||
const del = diffText.find("table").attr("del") * 1;
|
||||
table.find(".diffstat").text(add + del + " ").append(renderStatBar(add, del)).attr("title", add + " additions & " + del + " deletions").tooltip();
|
||||
$('span.diffstat[data-diff-id="' + i + '"]')
|
||||
.html('<span class="text-diff-added">+' + add + '</span><span class="text-diff-deleted">-' + del + '</span>')
|
||||
@@ -347,7 +372,7 @@ $(function(){
|
||||
});
|
||||
}
|
||||
@if(showLineNotes){
|
||||
var fileName = table.attr('filename');
|
||||
const fileName = table.attr('filename');
|
||||
$('.inline-comment').each(function(i, v) {
|
||||
if($(this).attr('filename') == fileName){
|
||||
renderOneCommitCommentIntoDiff($(this), table);
|
||||
@@ -358,13 +383,13 @@ $(function(){
|
||||
}
|
||||
|
||||
function renderReplyComment($table){
|
||||
var elements = {};
|
||||
var filename, newline, oldline;
|
||||
const elements = {};
|
||||
let filename, newline, oldline;
|
||||
$table.find('.comment-box-container .inline-comment').each(function(i, e){
|
||||
filename = $(e).attr('filename');
|
||||
newline = $(e).attr('newline');
|
||||
oldline = $(e).attr('oldline');
|
||||
var key = filename + '-' + newline + '-' + oldline;
|
||||
const key = filename + '-' + newline + '-' + oldline;
|
||||
elements[key] = {
|
||||
element: $(e),
|
||||
filename: filename,
|
||||
@@ -372,18 +397,18 @@ $(function(){
|
||||
oldline: oldline
|
||||
};
|
||||
});
|
||||
for(var key in elements){
|
||||
for(const key in elements){
|
||||
filename = elements[key]['filename'];
|
||||
oldline = elements[key]['oldline']; //? elements[key]['oldline'] : '';
|
||||
newline = elements[key]['newline']; //? elements[key]['newline'] : '';
|
||||
|
||||
var $v = $('<div class="commit-comment-box reply-comment-box">')
|
||||
const $v = $('<div class="commit-comment-box reply-comment-box">')
|
||||
.append($('<input type="text" class="form-control reply-comment" placeholder="Reply..." '
|
||||
+ 'data-filename="' + filename + '" '
|
||||
+ 'data-newline="' + newline + '" '
|
||||
+ 'data-oldline="' + oldline + '">'));
|
||||
|
||||
var tmp;
|
||||
let tmp;
|
||||
if (typeof oldline !== 'undefined') {
|
||||
if (typeof newline !== 'undefined') {
|
||||
tmp = getInlineContainer();
|
||||
@@ -399,16 +424,19 @@ $(function(){
|
||||
}
|
||||
}
|
||||
|
||||
function renderDiffs(){
|
||||
var i = 0, diffs = $('.diffText');
|
||||
function renderDiffs() {
|
||||
const diffs = $('.diffText');
|
||||
let i = 0;
|
||||
function render(){
|
||||
if(diffs[i]){
|
||||
var $table = renderOneDiff($(diffs[i]), window.viewType);
|
||||
if (diffs[i]) {
|
||||
const $table = renderOneDiff($(diffs[i]), window.viewType, $('.file-hash')[i].id);
|
||||
@if(hasWritePermission) {
|
||||
renderReplyComment($table);
|
||||
}
|
||||
i++;
|
||||
setTimeout(render);
|
||||
} else {
|
||||
updateHighlighting();
|
||||
}
|
||||
}
|
||||
render();
|
||||
@@ -416,7 +444,7 @@ $(function(){
|
||||
});
|
||||
|
||||
function changeDisplaySetting(key, value){
|
||||
var url = '';
|
||||
let url = '';
|
||||
window.params[key] = value;
|
||||
for(key in window.params){
|
||||
if(window.params[key] != ''){
|
||||
@@ -429,4 +457,47 @@ function changeDisplaySetting(key, value){
|
||||
}
|
||||
location.href = url;
|
||||
}
|
||||
|
||||
let scrolling = false;
|
||||
|
||||
/**
|
||||
* Highlight lines which are specified by URL hash.
|
||||
*/
|
||||
function updateHighlighting(){
|
||||
const hash = location.hash;
|
||||
$('tr.highlight').removeClass('highlight');
|
||||
if (hash.match(/#diff-(\w+)-[LR]\d+(-[LR]\d+)?/)) {
|
||||
const fragments = hash.substr(1).split('-');
|
||||
if (fragments.length == 3) {
|
||||
const tr = $('th#diff-' + fragments[1] + '-' + fragments[2]).closest('tr');
|
||||
tr.addClass('highlight');
|
||||
if(!scrolling){
|
||||
$(window).scrollTop($('th#diff-' + fragments[1] + '-' + fragments[2]).closest('tr').offset().top);
|
||||
}
|
||||
} else if (fragments.length > 3) {
|
||||
let highlight = false;
|
||||
$('th[id^=diff-' + fragments[1] + '-').each(function(i, th) {
|
||||
if (th.id.split('-')[2] == fragments[2]) { // start
|
||||
highlight = true;
|
||||
$(th.closest('tr')).addClass('highlight');
|
||||
} else if (highlight == true) {
|
||||
if (th.id.split('-')[2] == fragments[3]) { // end
|
||||
$(th.closest('tr')).addClass('highlight');
|
||||
return false;
|
||||
} else {
|
||||
$(th.closest('tr')).addClass('highlight');
|
||||
}
|
||||
}
|
||||
});
|
||||
if(!scrolling){
|
||||
$(window).scrollTop($('th#diff-' + fragments[1] + '-' + fragments[2]).closest('tr').offset().top);
|
||||
}
|
||||
}
|
||||
} else if (hash != ''){
|
||||
if (!scrolling) {
|
||||
$(window).scrollTop($(hash).offset().top);
|
||||
}
|
||||
}
|
||||
scrolling = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user