mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 04:26:32 +02:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a51b57af2a | ||
|
|
17ff024166 | ||
|
|
ed0c8e3f2c | ||
|
|
6b762b0693 | ||
|
|
62e6d0d6e8 | ||
|
|
6947f57bd8 | ||
|
|
08295afb51 | ||
|
|
8d5b494785 | ||
|
|
8d52fc06ed | ||
|
|
85b83af73f | ||
|
|
d99898e191 | ||
|
|
2044f5b838 | ||
|
|
bb804f6597 | ||
|
|
4d3756ac0a | ||
|
|
0739b3048b | ||
|
|
ff5a05511d | ||
|
|
186ce769a2 | ||
|
|
848c698491 | ||
|
|
41ea2087d1 | ||
|
|
1d77727867 | ||
|
|
051d059e5c | ||
|
|
324107beef | ||
|
|
801d71b6d2 | ||
|
|
106f7a41d8 | ||
|
|
1b46651c32 | ||
|
|
459b25e075 | ||
|
|
e3c3a61f0b | ||
|
|
a311ee5ef5 | ||
|
|
b13decf0e9 | ||
|
|
f6b92ef40b | ||
|
|
919b1d01e3 | ||
|
|
fe73c11611 | ||
|
|
c8af6c4b5a | ||
|
|
5d2d36dccf | ||
|
|
a11f711778 | ||
|
|
6920704caa | ||
|
|
451a6ef359 | ||
|
|
a93f4cc780 | ||
|
|
f9fda26e7a | ||
|
|
7435902b70 | ||
|
|
feb57c97b9 | ||
|
|
7021942a6e | ||
|
|
9251d64de8 | ||
|
|
d0c99727e9 | ||
|
|
1fbfcfb446 | ||
|
|
38328b2ffe | ||
|
|
3cce4e5308 | ||
|
|
757292d670 | ||
|
|
ef16804b49 | ||
|
|
dafabb6278 | ||
|
|
1f37362da4 | ||
|
|
1dba28d153 | ||
|
|
e83b017ef2 | ||
|
|
eeabbfd599 | ||
|
|
55a8602bba | ||
|
|
2e80e3baaf | ||
|
|
efdfe2b1b5 | ||
|
|
884fc5318a |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -2,7 +2,5 @@
|
|||||||
|
|
||||||
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues and pull requests whether there is a same request in the past.
|
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues and pull requests whether there is a same request in the past.
|
||||||
- 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. If you don't wanna waste your time to make a pull request, ask us about your idea at [gitter room](https://gitter.im/gitbucket/gitbucket) before staring your work.
|
- 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. If you don't wanna waste your time to make a pull request, ask us about your idea at [gitter room](https://gitter.im/gitbucket/gitbucket) before staring your work.
|
||||||
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
|
||||||
- You can edit the GitBucket documentation on Wiki if you have a GitHub account. When you find any mistakes or lacks in the documentation, please update it directly.
|
- You can edit the GitBucket documentation on Wiki if you have a GitHub account. When you find any mistakes or lacks in the documentation, please update it directly.
|
||||||
- Write an issue, a pull request, commit messages and comments in source code in English.
|
|
||||||
- All your contributions are handled as [Apache Software License, Version 2.0](https://github.com/gitbucket/gitbucket/blob/master/LICENSE). When you create a pull request or update the documentation, we assume you agreed this clause.
|
- All your contributions are handled as [Apache Software License, Version 2.0](https://github.com/gitbucket/gitbucket/blob/master/LICENSE). When you create a pull request or update the documentation, we assume you agreed this clause.
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@@ -16,4 +16,3 @@
|
|||||||
- *describe the problem and its symptoms*
|
- *describe the problem and its symptoms*
|
||||||
- *explain how to reproduce*
|
- *explain how to reproduce*
|
||||||
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
||||||
- *do your best to use a correct english (re-read yourself)*
|
|
||||||
|
|||||||
3
.github/SUPPORT.md
vendored
3
.github/SUPPORT.md
vendored
@@ -2,5 +2,4 @@
|
|||||||
|
|
||||||
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
- At first, 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 [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
- Write issues in English if it's possible. It enables many of contributors to help you.
|
||||||
- Write an issue in English. Since we can't support issues written in other languages, we close them forcibly.
|
|
||||||
|
|||||||
@@ -66,12 +66,17 @@ 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 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 [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
- We can also provide support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
|
||||||
- Write an issue in English. At least, write subject in English.
|
|
||||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
-------------
|
-------------
|
||||||
|
### 4.17.0 - 30 Sep 2017
|
||||||
|
- [gitbucket-ci-plugin](https://github.com/takezoe/gitbucket-ci-plugin) is available
|
||||||
|
- Transferring to URL with commit ID
|
||||||
|
- Drop uploadable file type limitation
|
||||||
|
- Improve Mailer API
|
||||||
|
- Web API and webhook enhancement
|
||||||
|
|
||||||
### 4.16.0 - 2 Sep 2017
|
### 4.16.0 - 2 Sep 2017
|
||||||
- Support AdminLTE color skin
|
- Support AdminLTE color skin
|
||||||
- Improve unexpected error handling
|
- Improve unexpected error handling
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
val Organization = "io.github.gitbucket"
|
val Organization = "io.github.gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "4.16.0"
|
val GitBucketVersion = "4.17.0"
|
||||||
val ScalatraVersion = "2.5.0"
|
val ScalatraVersion = "2.5.0"
|
||||||
val JettyVersion = "9.3.19.v20170502"
|
val JettyVersion = "9.3.19.v20170502"
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ libraryDependencies ++= Seq(
|
|||||||
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
|
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
|
||||||
"commons-io" % "commons-io" % "2.5",
|
"commons-io" % "commons-io" % "2.5",
|
||||||
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.14",
|
"io.github.gitbucket" % "markedj" % "1.0.15",
|
||||||
"org.apache.commons" % "commons-compress" % "1.13",
|
"org.apache.commons" % "commons-compress" % "1.13",
|
||||||
"org.apache.commons" % "commons-email" % "1.4",
|
"org.apache.commons" % "commons-email" % "1.4",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
|
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
|
||||||
|
|||||||
@@ -2,23 +2,15 @@ JRebel integration (optional)
|
|||||||
=============================
|
=============================
|
||||||
|
|
||||||
[JRebel](https://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
[JRebel](https://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
||||||
JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change:
|
JRebel is generally able to eliminate the need for the slow "app restart" per modification of codes. Alsp it's only used during development, and doesn't change your deployed app in any way.
|
||||||
|
|
||||||
```
|
JRebel is not open source, but we can use it free for non-commercial use.
|
||||||
> jetty:start
|
|
||||||
```
|
|
||||||
|
|
||||||
While JRebel is not open source, it does reload your code faster than the `~;copy-resources;aux-compile` way of doing things using `sbt`.
|
|
||||||
|
|
||||||
It's only used during development, and doesn't change your deployed app in any way.
|
|
||||||
|
|
||||||
JRebel used to be free for Scala developers, but that changed recently, and now there's a cost associated with usage for Scala. There are trial plans and free non-commercial licenses available if you just want to try it out.
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
## 1. Get a JRebel license
|
## 1. Get a JRebel license
|
||||||
|
|
||||||
Sign up for a [usage plan](https://my.jrebel.com/). You will need to create an account.
|
Sign up for a [myJRebel](https://my.jrebel.com/register). You will need to create an account.
|
||||||
|
|
||||||
## 2. Download JRebel
|
## 2. Download JRebel
|
||||||
|
|
||||||
@@ -27,9 +19,7 @@ Next, unzip the downloaded file.
|
|||||||
|
|
||||||
## 3. Activate
|
## 3. Activate
|
||||||
|
|
||||||
Follow the [instructions on the JRebel website](https://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
|
Follow `readme.txt` in the extracted directory to activate your downloaded JRebel.
|
||||||
|
|
||||||
You can use the default settings for all the configurations.
|
|
||||||
|
|
||||||
You don't need to integrate with your IDE, since we're using sbt to do the servlet deployment.
|
You don't need to integrate with your IDE, since we're using sbt to do the servlet deployment.
|
||||||
|
|
||||||
@@ -41,13 +31,13 @@ You only need to tell jvm where to find the jrebel jar.
|
|||||||
To do so, edit your shell resource file (usually `~/.bash_profile` on Mac, and `~/.bashrc` on Linux), and add the following line:
|
To do so, edit your shell resource file (usually `~/.bash_profile` on Mac, and `~/.bashrc` on Linux), and add the following line:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export JREBEL=/path/to/jrebel/jrebel.jar
|
export JREBEL=/path/to/jrebel/legacy/jrebel.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, if you unzipped your JRebel download in your home directory, you whould use:
|
For example, if you unzipped your JRebel download in your home directory, you whould use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export JREBEL=~/jrebel/jrebel.jar
|
export JREBEL=~/jrebel/legacy/jrebel.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
Now reload your shell:
|
Now reload your shell:
|
||||||
@@ -73,39 +63,26 @@ $ ./sbt
|
|||||||
You will start the servlet container slightly differently now that you're using sbt.
|
You will start the servlet container slightly differently now that you're using sbt.
|
||||||
|
|
||||||
```
|
```
|
||||||
> jetty:start
|
> jetty:quickstart
|
||||||
:
|
:
|
||||||
[info] starting server ...
|
2017-09-21 15:46:35 JRebel:
|
||||||
[success] Total time: 3 s, completed Jan 3, 2016 9:47:55 PM
|
2017-09-21 15:46:35 JRebel: #############################################################
|
||||||
2016-01-03 21:47:57 JRebel:
|
2017-09-21 15:46:35 JRebel:
|
||||||
2016-01-03 21:47:57 JRebel: A newer version '6.3.1' is available for download
|
2017-09-21 15:46:35 JRebel: Legacy Agent 7.0.15 (201709080836)
|
||||||
2016-01-03 21:47:57 JRebel: from http://zeroturnaround.com/software/jrebel/download/
|
2017-09-21 15:46:35 JRebel: (c) Copyright ZeroTurnaround AS, Estonia, Tartu.
|
||||||
2016-01-03 21:47:57 JRebel:
|
2017-09-21 15:46:35 JRebel:
|
||||||
2016-01-03 21:47:58 JRebel: Contacting myJRebel server ..
|
2017-09-21 15:46:35 JRebel: Over the last 2 days JRebel prevented
|
||||||
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/classes' will be monitored for changes.
|
2017-09-21 15:46:35 JRebel: at least 8 redeploys/restarts saving you about 0.3 hours.
|
||||||
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/test-classes' will be monitored for changes.
|
2017-09-21 15:46:35 JRebel:
|
||||||
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/webapp' will be monitored for changes.
|
2017-09-21 15:46:35 JRebel: Licensed to Naoki Takezoe (using myJRebel).
|
||||||
2016-01-03 21:48:00 JRebel:
|
2017-09-21 15:46:35 JRebel:
|
||||||
2016-01-03 21:48:00 JRebel: #############################################################
|
2017-09-21 15:46:35 JRebel:
|
||||||
2016-01-03 21:48:00 JRebel:
|
2017-09-21 15:46:35 JRebel: #############################################################
|
||||||
2016-01-03 21:48:00 JRebel: JRebel Legacy Agent 6.2.5 (201509291538)
|
2017-09-21 15:46:35 JRebel:
|
||||||
2016-01-03 21:48:00 JRebel: (c) Copyright ZeroTurnaround AS, Estonia, Tartu.
|
|
||||||
2016-01-03 21:48:00 JRebel:
|
|
||||||
2016-01-03 21:48:00 JRebel: Over the last 30 days JRebel prevented
|
|
||||||
2016-01-03 21:48:00 JRebel: at least 182 redeploys/restarts saving you about 7.4 hours.
|
|
||||||
2016-01-03 21:48:00 JRebel:
|
|
||||||
2016-01-03 21:48:00 JRebel: Over the last 324 days JRebel prevented
|
|
||||||
2016-01-03 21:48:00 JRebel: at least 1538 redeploys/restarts saving you about 62.4 hours.
|
|
||||||
2016-01-03 21:48:00 JRebel:
|
|
||||||
2016-01-03 21:48:00 JRebel: Licensed to nazo king (using myJRebel).
|
|
||||||
2016-01-03 21:48:00 JRebel:
|
|
||||||
2016-01-03 21:48:00 JRebel:
|
|
||||||
2016-01-03 21:48:00 JRebel: #############################################################
|
|
||||||
2016-01-03 21:48:00 JRebel:
|
|
||||||
:
|
:
|
||||||
|
|
||||||
> ~ copy-resources
|
> ~compile
|
||||||
[success] Total time: 0 s, completed Jan 3, 2016 9:13:54 PM
|
[success] Total time: 2 s, completed 2017/09/21 15:50:06
|
||||||
1. Waiting for source changes... (press enter to interrupt)
|
1. Waiting for source changes... (press enter to interrupt)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -114,12 +91,11 @@ For example, you can change the title on `src/main/twirl/gitbucket/core/main.sca
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
:
|
:
|
||||||
<a class="navbar-brand" href="@path/">
|
<a href="@context.path/" class="logo">
|
||||||
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket
|
<img src="@helpers.assets("/common/images/gitbucket.svg")" style="width: 24px; height: 24px; display: inline;"/>
|
||||||
@defining(AutoUpdate.getCurrentVersion){ version =>
|
GitBucket
|
||||||
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
|
||||||
}
|
|
||||||
change code !!!!!!!!!!!!!!!!
|
change code !!!!!!!!!!!!!!!!
|
||||||
|
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
|
||||||
</a>
|
</a>
|
||||||
:
|
:
|
||||||
```
|
```
|
||||||
@@ -128,21 +104,17 @@ If JRebel is doing is correctly installed you will see a notice for you:
|
|||||||
|
|
||||||
```
|
```
|
||||||
1. Waiting for source changes... (press enter to interrupt)
|
1. Waiting for source changes... (press enter to interrupt)
|
||||||
2016-01-03 21:48:42 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
[info] Compiling 1 Scala source to /Users/naoki.takezoe/gitbucket/target/scala-2.12/classes...
|
||||||
[info] Wrote rebel.xml to /git/gitbucket/target/scala-2.11/resource_managed/main/rebel.xml
|
[success] Total time: 1 s, completed 2017/09/21 15:55:40
|
||||||
[info] Compiling 1 Scala source to /git/gitbucket/target/scala-2.11/classes...
|
|
||||||
[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM
|
|
||||||
2. Waiting for source changes... (press enter to interrupt)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And you reload browser, JRebel give notice of that it has reloaded classes:
|
And you reload browser, JRebel give notice of that it has reloaded classes:
|
||||||
|
|
||||||
```
|
```
|
||||||
[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM
|
|
||||||
2. Waiting for source changes... (press enter to interrupt)
|
2. Waiting for source changes... (press enter to interrupt)
|
||||||
2016-01-03 21:49:13 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
2017-09-21 15:55:40 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6. Limitations
|
## 6. Limitations
|
||||||
|
|
||||||
JRebel is nearly always able to eliminate the need to explicitly reload your container after a code change. However, if you change any of your routes patterns, there is nothing JRebel can do, you will have to run `jetty:start`.
|
JRebel is nearly always able to eliminate the need to explicitly reload your container after a code change. However, if you change any of your routing patterns, there is nothing JRebel can do, you will have to restart by `jetty:quickstart`.
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"description": "Provides notifications feature on GitBucket.",
|
"description": "Provides notifications feature on GitBucket.",
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"range": ">=4.16.0",
|
"range": ">=4.17.0",
|
||||||
"file": "gitbucket-notifications-plugin_2.12-1.1.0.jar"
|
"file": "gitbucket-notifications-plugin_2.12-1.2.0.jar"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default": true
|
"default": true
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.0")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.7")
|
||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")
|
||||||
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.1")
|
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "4.0.0")
|
||||||
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3")
|
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
|
||||||
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M15")
|
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC11")
|
||||||
|
|||||||
@@ -41,5 +41,6 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
|||||||
),
|
),
|
||||||
new Version("4.14.1"),
|
new Version("4.14.1"),
|
||||||
new Version("4.15.0"),
|
new Version("4.15.0"),
|
||||||
new Version("4.16.0")
|
new Version("4.16.0"),
|
||||||
|
new Version("4.17.0")
|
||||||
)
|
)
|
||||||
|
|||||||
124
src/main/scala/gitbucket/core/api/ApiCommits.scala
Normal file
124
src/main/scala/gitbucket/core/api/ApiCommits.scala
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo}
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||||
|
import ApiCommits._
|
||||||
|
|
||||||
|
case class ApiCommits(
|
||||||
|
url: ApiPath,
|
||||||
|
sha: String,
|
||||||
|
html_url: ApiPath,
|
||||||
|
comment_url: ApiPath,
|
||||||
|
commit: Commit,
|
||||||
|
author: ApiUser,
|
||||||
|
committer: ApiUser,
|
||||||
|
parents: Seq[Tree],
|
||||||
|
stats: Stats,
|
||||||
|
files: Seq[File]
|
||||||
|
)
|
||||||
|
|
||||||
|
object ApiCommits {
|
||||||
|
case class Commit(
|
||||||
|
url: ApiPath,
|
||||||
|
author: ApiPersonIdent,
|
||||||
|
committer: ApiPersonIdent,
|
||||||
|
message: String,
|
||||||
|
comment_count: Int,
|
||||||
|
tree: Tree
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Tree(
|
||||||
|
url: ApiPath,
|
||||||
|
sha: String
|
||||||
|
)
|
||||||
|
|
||||||
|
case class Stats(
|
||||||
|
additions: Int,
|
||||||
|
deletions: Int,
|
||||||
|
total: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
case class File(
|
||||||
|
filename: String,
|
||||||
|
additions: Int,
|
||||||
|
deletions: Int,
|
||||||
|
changes: Int,
|
||||||
|
status: String,
|
||||||
|
raw_url: ApiPath,
|
||||||
|
blob_url: ApiPath,
|
||||||
|
patch: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def apply(repositoryName: RepositoryName, commitInfo: CommitInfo, diffs: Seq[DiffInfo], author: Account, committer: Account,
|
||||||
|
commentCount: Int): ApiCommits = {
|
||||||
|
val files = diffs.map { diff =>
|
||||||
|
var additions = 0
|
||||||
|
var deletions = 0
|
||||||
|
|
||||||
|
diff.patch.getOrElse("").split("\n").foreach { line =>
|
||||||
|
if(line.startsWith("+")) additions = additions + 1
|
||||||
|
if(line.startsWith("-")) deletions = deletions + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
File(
|
||||||
|
filename = if(diff.changeType == ChangeType.DELETE){ diff.oldPath } else { diff.newPath },
|
||||||
|
additions = additions,
|
||||||
|
deletions = deletions,
|
||||||
|
changes = additions + deletions,
|
||||||
|
status = diff.changeType match {
|
||||||
|
case ChangeType.ADD => "added"
|
||||||
|
case ChangeType.MODIFY => "modified"
|
||||||
|
case ChangeType.DELETE => "deleted"
|
||||||
|
case ChangeType.RENAME => "renamed"
|
||||||
|
case ChangeType.COPY => "copied"
|
||||||
|
},
|
||||||
|
raw_url = if(diff.changeType == ChangeType.DELETE){
|
||||||
|
ApiPath(s"/${repositoryName.fullName}/raw/${commitInfo.parents.head}/${diff.oldPath}")
|
||||||
|
} else {
|
||||||
|
ApiPath(s"/${repositoryName.fullName}/raw/${commitInfo.id}/${diff.newPath}")
|
||||||
|
},
|
||||||
|
blob_url = if(diff.changeType == ChangeType.DELETE){
|
||||||
|
ApiPath(s"/${repositoryName.fullName}/blob/${commitInfo.parents.head}/${diff.oldPath}")
|
||||||
|
} else {
|
||||||
|
ApiPath(s"/${repositoryName.fullName}/blob/${commitInfo.id}/${diff.newPath}")
|
||||||
|
},
|
||||||
|
patch = diff.patch.getOrElse("")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiCommits(
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),
|
||||||
|
sha = commitInfo.id,
|
||||||
|
html_url = ApiPath(s"${repositoryName.fullName}/commit/${commitInfo.id}"),
|
||||||
|
comment_url = ApiPath(""),
|
||||||
|
commit = Commit(
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),
|
||||||
|
author = ApiPersonIdent.author(commitInfo),
|
||||||
|
committer = ApiPersonIdent.committer(commitInfo),
|
||||||
|
message = commitInfo.shortMessage,
|
||||||
|
comment_count = commentCount,
|
||||||
|
tree = Tree(
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/tree/${commitInfo.id}"), // TODO This endpoint has not been implemented yet.
|
||||||
|
sha = commitInfo.id
|
||||||
|
)
|
||||||
|
),
|
||||||
|
author = ApiUser(author),
|
||||||
|
committer = ApiUser(committer),
|
||||||
|
parents = commitInfo.parents.map { parent =>
|
||||||
|
Tree(
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/tree/${parent}"), // TODO This endpoint has not been implemented yet.
|
||||||
|
sha = parent
|
||||||
|
)
|
||||||
|
},
|
||||||
|
stats = Stats(
|
||||||
|
additions = files.map(_.additions).sum,
|
||||||
|
deletions = files.map(_.deletions).sum,
|
||||||
|
total = files.map(_.additions).sum + files.map(_.deletions).sum
|
||||||
|
),
|
||||||
|
files = files
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
package gitbucket.core.api
|
package gitbucket.core.api
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* path for api url. if set path '/repos/aa/bb' then, expand 'http://server:post/repos/aa/bb' when converted to json.
|
* Path for API url.
|
||||||
|
* If set path '/repos/aa/bb' then, expand 'http://server:port/repos/aa/bb' when converted to json.
|
||||||
*/
|
*/
|
||||||
case class ApiPath(path: String)
|
case class ApiPath(path: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path for git repository via SSH.
|
||||||
|
* If set path '/aa/bb.git' then, expand 'git@server:port/aa/bb.git' when converted to json.
|
||||||
|
*/
|
||||||
|
case class SshPath(path: String)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package gitbucket.core.api
|
|||||||
import gitbucket.core.model.{Account, Issue, IssueComment, PullRequest}
|
import gitbucket.core.model.{Account, Issue, IssueComment, PullRequest}
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://developer.github.com/v3/pulls/
|
* https://developer.github.com/v3/pulls/
|
||||||
*/
|
*/
|
||||||
@@ -19,7 +18,8 @@ case class ApiPullRequest(
|
|||||||
merged_by: Option[ApiUser],
|
merged_by: Option[ApiUser],
|
||||||
title: String,
|
title: String,
|
||||||
body: String,
|
body: String,
|
||||||
user: ApiUser) {
|
user: ApiUser,
|
||||||
|
assignee: Option[ApiUser]){
|
||||||
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
|
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
|
||||||
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
|
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
|
||||||
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
|
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
|
||||||
@@ -39,6 +39,7 @@ object ApiPullRequest{
|
|||||||
headRepo: ApiRepository,
|
headRepo: ApiRepository,
|
||||||
baseRepo: ApiRepository,
|
baseRepo: ApiRepository,
|
||||||
user: ApiUser,
|
user: ApiUser,
|
||||||
|
assignee: Option[ApiUser],
|
||||||
mergedComment: Option[(IssueComment, Account)]
|
mergedComment: Option[(IssueComment, Account)]
|
||||||
): ApiPullRequest =
|
): ApiPullRequest =
|
||||||
ApiPullRequest(
|
ApiPullRequest(
|
||||||
@@ -59,14 +60,16 @@ object ApiPullRequest{
|
|||||||
merged_by = mergedComment.map { case (_, account) => ApiUser(account) },
|
merged_by = mergedComment.map { case (_, account) => ApiUser(account) },
|
||||||
title = issue.title,
|
title = issue.title,
|
||||||
body = issue.content.getOrElse(""),
|
body = issue.content.getOrElse(""),
|
||||||
user = user
|
user = user,
|
||||||
|
assignee = assignee
|
||||||
)
|
)
|
||||||
|
|
||||||
case class Commit(
|
case class Commit(
|
||||||
sha: String,
|
sha: String,
|
||||||
ref: String,
|
ref: String,
|
||||||
repo: ApiRepository)(baseOwner:String){
|
repo: ApiRepository)(baseOwner:String){
|
||||||
val label = if( baseOwner == repo.owner.login ){ ref }else{ s"${repo.owner.login}:${ref}" }
|
val label = if( baseOwner == repo.owner.login ){ ref } else { s"${repo.owner.login}:${ref}" }
|
||||||
val user = repo.owner
|
val user = repo.owner
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ case class ApiRepository(
|
|||||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||||
val clone_url = ApiPath(s"/git/${full_name}.git")
|
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||||
val html_url = ApiPath(s"/${full_name}")
|
val html_url = ApiPath(s"/${full_name}")
|
||||||
|
val ssh_url = Some(SshPath(s":${full_name}.git"))
|
||||||
}
|
}
|
||||||
|
|
||||||
object ApiRepository{
|
object ApiRepository{
|
||||||
@@ -55,12 +56,13 @@ object ApiRepository{
|
|||||||
|
|
||||||
def forDummyPayload(owner: ApiUser): ApiRepository =
|
def forDummyPayload(owner: ApiUser): ApiRepository =
|
||||||
ApiRepository(
|
ApiRepository(
|
||||||
name="dummy",
|
name = "dummy",
|
||||||
full_name=s"${owner.login}/dummy",
|
full_name = s"${owner.login}/dummy",
|
||||||
description="",
|
description = "",
|
||||||
watchers=0,
|
watchers = 0,
|
||||||
forks=0,
|
forks = 0,
|
||||||
`private`=false,
|
`private` = false,
|
||||||
default_branch="master",
|
default_branch = "master",
|
||||||
owner=owner)(true)
|
owner = owner
|
||||||
|
)(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import scala.util.Try
|
|||||||
|
|
||||||
object JsonFormat {
|
object JsonFormat {
|
||||||
|
|
||||||
case class Context(baseUrl: String)
|
case class Context(baseUrl: String, sshUrl: Option[String])
|
||||||
|
|
||||||
val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||||
|
|
||||||
@@ -33,23 +33,31 @@ object JsonFormat {
|
|||||||
FieldSerializer[ApiComment]() +
|
FieldSerializer[ApiComment]() +
|
||||||
FieldSerializer[ApiContents]() +
|
FieldSerializer[ApiContents]() +
|
||||||
FieldSerializer[ApiLabel]() +
|
FieldSerializer[ApiLabel]() +
|
||||||
|
FieldSerializer[ApiCommits]() +
|
||||||
|
FieldSerializer[ApiCommits.Commit]() +
|
||||||
|
FieldSerializer[ApiCommits.Tree]() +
|
||||||
|
FieldSerializer[ApiCommits.Stats]() +
|
||||||
|
FieldSerializer[ApiCommits.File]() +
|
||||||
ApiBranchProtection.enforcementLevelSerializer
|
ApiBranchProtection.enforcementLevelSerializer
|
||||||
|
|
||||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](_ => ({
|
||||||
(
|
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
|
||||||
{
|
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||||
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
|
}, {
|
||||||
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
case ApiPath(path) => JString(c.baseUrl + path)
|
||||||
},
|
}))
|
||||||
{
|
|
||||||
case ApiPath(path) => JString(c.baseUrl + path)
|
def sshPathSerializer(c: Context) = new CustomSerializer[SshPath](_ => ({
|
||||||
}
|
case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) => SshPath(s.substring(c.sshUrl.get.length))
|
||||||
)
|
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||||
)
|
}, {
|
||||||
|
case SshPath(path) => c.sshUrl.map { sshUrl => JString(sshUrl + path) } getOrElse JNothing
|
||||||
|
}))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert object to json string
|
* convert object to json string
|
||||||
*/
|
*/
|
||||||
def apply(obj: AnyRef)(implicit c: Context): String = Serialization.write(obj)(jsonFormats + apiPathSerializer(c))
|
def apply(obj: AnyRef)(implicit c: Context): String =
|
||||||
|
Serialization.write(obj)(jsonFormats + apiPathSerializer(c) + sshPathSerializer(c))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import gitbucket.core.util.SyntaxSugars._
|
|||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
@@ -50,6 +51,7 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
with LabelsService
|
with LabelsService
|
||||||
with MilestonesService
|
with MilestonesService
|
||||||
with PullRequestService
|
with PullRequestService
|
||||||
|
with CommitsService
|
||||||
with CommitStatusService
|
with CommitStatusService
|
||||||
with RepositoryCreationService
|
with RepositoryCreationService
|
||||||
with IssueCreationService
|
with IssueCreationService
|
||||||
@@ -499,7 +501,7 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
val condition = IssueSearchCondition(request)
|
val condition = IssueSearchCondition(request)
|
||||||
val baseOwner = getAccountByUserName(repository.owner).get
|
val baseOwner = getAccountByUserName(repository.owner).get
|
||||||
|
|
||||||
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account)] =
|
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] =
|
||||||
searchPullRequestByApi(
|
searchPullRequestByApi(
|
||||||
condition = condition,
|
condition = condition,
|
||||||
offset = (page - 1) * PullRequestLimit,
|
offset = (page - 1) * PullRequestLimit,
|
||||||
@@ -507,13 +509,14 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
repos = repository.owner -> repository.name
|
repos = repository.owner -> repository.name
|
||||||
)
|
)
|
||||||
|
|
||||||
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
|
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
|
||||||
ApiPullRequest(
|
ApiPullRequest(
|
||||||
issue = issue,
|
issue = issue,
|
||||||
pullRequest = pullRequest,
|
pullRequest = pullRequest,
|
||||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
user = ApiUser(issueUser),
|
user = ApiUser(issueUser),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -530,6 +533,7 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
baseOwner <- users.get(repository.owner)
|
baseOwner <- users.get(repository.owner)
|
||||||
headOwner <- users.get(pullRequest.requestUserName)
|
headOwner <- users.get(pullRequest.requestUserName)
|
||||||
issueUser <- users.get(issue.openedUserName)
|
issueUser <- users.get(issue.openedUserName)
|
||||||
|
assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) }
|
||||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||||
} yield {
|
} yield {
|
||||||
JsonFormat(ApiPullRequest(
|
JsonFormat(ApiPullRequest(
|
||||||
@@ -538,6 +542,7 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
user = ApiUser(issueUser),
|
user = ApiUser(issueUser),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
))
|
))
|
||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
@@ -628,6 +633,52 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/commits/#get-a-single-commit
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/commits/:sha")(referrersOnly { repository =>
|
||||||
|
val owner = repository.owner
|
||||||
|
val name = repository.name
|
||||||
|
val sha = params("sha")
|
||||||
|
|
||||||
|
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||||
|
val repo = git.getRepository
|
||||||
|
val objectId = repo.resolve(sha)
|
||||||
|
val commitInfo = using(new RevWalk(repo)){ revWalk =>
|
||||||
|
new CommitInfo(revWalk.parseCommit(objectId))
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonFormat(ApiCommits(
|
||||||
|
repositoryName = RepositoryName(repository),
|
||||||
|
commitInfo = commitInfo,
|
||||||
|
diffs = JGitUtil.getDiffs(git, commitInfo.parents.head, commitInfo.id, false, true),
|
||||||
|
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||||
|
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||||
|
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private def getAccount(userName: String, email: String): Account = {
|
||||||
|
getAccountByMailAddress(email).getOrElse {
|
||||||
|
Account(
|
||||||
|
userName = userName,
|
||||||
|
fullName = userName,
|
||||||
|
mailAddress = email,
|
||||||
|
password = "xxx",
|
||||||
|
isAdmin = false,
|
||||||
|
url = None,
|
||||||
|
registeredDate = new java.util.Date(),
|
||||||
|
updatedDate = new java.util.Date(),
|
||||||
|
lastLoginDate = None,
|
||||||
|
image = None,
|
||||||
|
isGroupAccount = false,
|
||||||
|
isRemoved = true,
|
||||||
|
description = None
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
|
|||||||
FileUtils.writeByteArrayToFile(new java.io.File(
|
FileUtils.writeByteArrayToFile(new java.io.File(
|
||||||
getAttachedDir(params("owner"), params("repository")),
|
getAttachedDir(params("owner"), params("repository")),
|
||||||
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
|
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
|
||||||
}, FileUtil.isUploadableType)
|
}, _ => true)
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/wiki/:owner/:repository"){
|
post("/wiki/:owner/:repository"){
|
||||||
@@ -85,7 +85,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
|
|||||||
fileName
|
fileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, FileUtil.isUploadableType)
|
}, _ => true)
|
||||||
}
|
}
|
||||||
} getOrElse BadRequest()
|
} getOrElse BadRequest()
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
private def execute(f: (FileItem, String) => Unit , mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
||||||
case Some(file) if(mimeTypeChcker(file.name)) =>
|
case Some(file) if(mimeTypeChcker(file.name)) =>
|
||||||
defining(FileUtil.generateFileId){ fileId =>
|
defining(FileUtil.generateFileId){ fileId =>
|
||||||
f(file, fileId)
|
f(file, fileId)
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
self: RepositoryService with ActivityService with AccountService with RepositorySearchService
|
self: RepositoryService with ActivityService with AccountService with RepositorySearchService
|
||||||
with UsersAuthenticator with ReferrerAuthenticator =>
|
with UsersAuthenticator with ReferrerAuthenticator =>
|
||||||
|
|
||||||
case class SignInForm(userName: String, password: String)
|
case class SignInForm(userName: String, password: String, hash: Option[String])
|
||||||
|
|
||||||
val signinForm = mapping(
|
val signinForm = mapping(
|
||||||
"userName" -> trim(label("Username", text(required))),
|
"userName" -> trim(label("Username", text(required))),
|
||||||
"password" -> trim(label("Password", text(required)))
|
"password" -> trim(label("Password", text(required))),
|
||||||
|
"hash" -> trim(optional(text()))
|
||||||
)(SignInForm.apply)
|
)(SignInForm.apply)
|
||||||
|
|
||||||
// val searchForm = mapping(
|
// val searchForm = mapping(
|
||||||
@@ -54,7 +55,7 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
post("/signin", signinForm){ form =>
|
post("/signin", signinForm){ form =>
|
||||||
authenticate(context.settings, form.userName, form.password) match {
|
authenticate(context.settings, form.userName, form.password) match {
|
||||||
case Some(account) => signin(account)
|
case Some(account) => signin(account, form.hash)
|
||||||
case None => {
|
case None => {
|
||||||
flash += "userName" -> form.userName
|
flash += "userName" -> form.userName
|
||||||
flash += "password" -> form.password
|
flash += "password" -> form.password
|
||||||
@@ -86,7 +87,7 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Set account information into HttpSession and redirect.
|
* Set account information into HttpSession and redirect.
|
||||||
*/
|
*/
|
||||||
private def signin(account: Account) = {
|
private def signin(account: Account, hash: Option[String]) = {
|
||||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||||
updateLastLoginDate(account.userName)
|
updateLastLoginDate(account.userName)
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
||||||
redirect("/")
|
redirect("/")
|
||||||
} else {
|
} else {
|
||||||
redirect(redirectUrl)
|
redirect(redirectUrl + hash.getOrElse(""))
|
||||||
}
|
}
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
redirect("/")
|
redirect("/")
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ trait PreProcessControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||||
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||||
!context.currentPath.startsWith("/register")) {
|
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs")) {
|
||||||
Unauthorized()
|
Unauthorized()
|
||||||
} else {
|
} else {
|
||||||
pass()
|
pass()
|
||||||
|
|||||||
@@ -324,8 +324,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
|
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
|
||||||
val Seq(origin, forked) = multiParams("splat")
|
val Seq(origin, forked) = multiParams("splat")
|
||||||
val (originOwner, originId) = parseCompareIdentifie(origin, forkedRepository.owner)
|
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
|
||||||
val (forkedOwner, forkedId) = parseCompareIdentifie(forked, forkedRepository.owner)
|
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
|
||||||
|
|
||||||
(for(
|
(for(
|
||||||
originRepositoryName <- if(originOwner == forkedOwner) {
|
originRepositoryName <- if(originOwner == forkedOwner) {
|
||||||
@@ -411,8 +411,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
|
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
|
||||||
val Seq(origin, forked) = multiParams("splat")
|
val Seq(origin, forked) = multiParams("splat")
|
||||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
|
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
|
||||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
|
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner)
|
||||||
|
|
||||||
(for(
|
(for(
|
||||||
originRepositoryName <- if(originOwner == forkedOwner){
|
originRepositoryName <- if(originOwner == forkedOwner){
|
||||||
@@ -505,7 +505,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
* - "owner:branch" to ("owner", "branch")
|
* - "owner:branch" to ("owner", "branch")
|
||||||
* - "branch" to ("defaultOwner", "branch")
|
* - "branch" to ("defaultOwner", "branch")
|
||||||
*/
|
*/
|
||||||
private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) =
|
private def parseCompareIdentifier(value: String, defaultOwner: String): (String, String) =
|
||||||
if(value.contains(':')){
|
if(value.contains(':')){
|
||||||
val array = value.split(":")
|
val array = value.split(":")
|
||||||
(array(0), array(1))
|
(array(0), array(1))
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
|
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
git.gc()
|
git.gc().call()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flash += "info" -> "Garbage collection has been executed."
|
flash += "info" -> "Garbage collection has been executed."
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
try {
|
try {
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) { revCommit =>
|
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) { revCommit =>
|
||||||
JGitUtil.getDiffs(git, id) match {
|
JGitUtil.getDiffs(git, id, false) match {
|
||||||
case (diffs, oldCommitId) =>
|
case (diffs, oldCommitId) =>
|
||||||
html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||||
@@ -478,9 +478,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
|
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
|
||||||
form.issueId match {
|
form.issueId match {
|
||||||
case Some(issueId) =>
|
case Some(issueId) =>
|
||||||
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
|
getPullRequest(repository.owner, repository.name, issueId).foreach { case (issue, pullRequest) =>
|
||||||
callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get)
|
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
|
||||||
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, form.content, issue, repository))
|
||||||
|
callPullRequestReviewCommentWebHook("create", comment, repository, issue, pullRequest, context.baseUrl, context.loginAccount.get)
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
||||||
}
|
}
|
||||||
helper.html.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
helper.html.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -174,10 +174,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
||||||
try {
|
try {
|
||||||
new Mailer(form.smtp).send(form.testAddress,
|
new Mailer(context.settings.copy(smtp = Some(form.smtp), notification = true)).send(
|
||||||
"Test message from GitBucket", context.loginAccount.get,
|
to = form.testAddress,
|
||||||
"This is a test message from GitBucket.", None
|
subject = "Test message from GitBucket",
|
||||||
)
|
textMsg = "This is a test message from GitBucket.",
|
||||||
|
htmlMsg = None,
|
||||||
|
loginAccount = context.loginAccount
|
||||||
|
)
|
||||||
|
|
||||||
"Test mail has been sent to: " + form.testAddress
|
"Test mail has been sent to: " + form.testAddress
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true, false).filter(_.newPath == pageName + ".md"), repository,
|
||||||
isEditable(repository), flash.get("info"))
|
isEditable(repository), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -85,7 +85,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true, false), repository,
|
||||||
isEditable(repository), flash.get("info"))
|
isEditable(repository), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,18 +3,20 @@ package gitbucket.core.plugin
|
|||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
import gitbucket.core.model.Issue
|
import gitbucket.core.model.Issue
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import profile.api._
|
||||||
|
|
||||||
trait IssueHook {
|
trait IssueHook {
|
||||||
|
|
||||||
def created(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = ()
|
def created(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||||
def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = ()
|
def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||||
def closed(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = ()
|
def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||||
def reopened(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = ()
|
def reopened(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait PullRequestHook extends IssueHook {
|
trait PullRequestHook extends IssueHook {
|
||||||
|
|
||||||
def merged(issue: Issue, repository: RepositoryInfo)(implicit context: Context): Unit = ()
|
def merged(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,13 +257,14 @@ object PluginRegistry {
|
|||||||
if(installedDir.exists){
|
if(installedDir.exists){
|
||||||
FileUtils.deleteDirectory(installedDir)
|
FileUtils.deleteDirectory(installedDir)
|
||||||
}
|
}
|
||||||
installedDir.mkdir()
|
installedDir.mkdirs()
|
||||||
|
|
||||||
val pluginJars = listPluginJars(pluginDir)
|
val pluginJars = listPluginJars(pluginDir)
|
||||||
val extraJars = extraPluginDir.map { extraDir => listPluginJars(new File(extraDir)) }.getOrElse(Nil)
|
val extraJars = extraPluginDir.map { extraDir => listPluginJars(new File(extraDir)) }.getOrElse(Nil)
|
||||||
|
|
||||||
(extraJars ++ pluginJars).foreach { pluginJar =>
|
(extraJars ++ pluginJars).foreach { pluginJar =>
|
||||||
val installedJar = new File(installedDir, pluginJar.getName)
|
val installedJar = new File(installedDir, pluginJar.getName)
|
||||||
|
|
||||||
FileUtils.copyFile(pluginJar, installedJar)
|
FileUtils.copyFile(pluginJar, installedJar)
|
||||||
|
|
||||||
logger.info(s"Initialize ${pluginJar.getName}")
|
logger.info(s"Initialize ${pluginJar.getName}")
|
||||||
@@ -400,12 +401,15 @@ class PluginWatchThread(context: ServletContext, dir: String) extends Thread wit
|
|||||||
events.foreach { event =>
|
events.foreach { event =>
|
||||||
logger.info(event.kind + ": " + event.context)
|
logger.info(event.kind + ": " + event.context)
|
||||||
}
|
}
|
||||||
|
new Thread {
|
||||||
gitbucket.core.servlet.Database() withTransaction { session =>
|
override def run(): Unit = {
|
||||||
logger.info("Reloading plugins...")
|
gitbucket.core.servlet.Database() withTransaction { session =>
|
||||||
PluginRegistry.reload(context, loadSystemSettings(), session.conn)
|
logger.info("Reloading plugins...")
|
||||||
logger.info("Reloading finished.")
|
PluginRegistry.reload(context, loadSystemSettings(), session.conn)
|
||||||
}
|
logger.info("Reloading finished.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
}
|
}
|
||||||
detectedWatchKey.reset()
|
detectedWatchKey.reset()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,15 +202,16 @@ trait IssuesService {
|
|||||||
* @return (issue, issueUser, commentCount, pullRequest, headRepo, headOwner)
|
* @return (issue, issueUser, commentCount, pullRequest, headRepo, headOwner)
|
||||||
*/
|
*/
|
||||||
def searchPullRequestByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)
|
def searchPullRequestByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)
|
||||||
(implicit s: Session): List[(Issue, Account, Int, PullRequest, Repository, Account)] = {
|
(implicit s: Session): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = {
|
||||||
// get issues and comment count and labels
|
// get issues and comment count and labels
|
||||||
searchIssueQueryBase(condition, true, offset, limit, repos)
|
searchIssueQueryBase(condition, true, offset, limit, repos)
|
||||||
.join(PullRequests).on { case t1 ~ t2 ~ i ~ t3 => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) }
|
.join (PullRequests).on { case t1 ~ t2 ~ i ~ t3 => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) }
|
||||||
.join(Repositories).on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.byRepository(t1.userName, t1.repositoryName) }
|
.join (Repositories).on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.byRepository(t1.userName, t1.repositoryName) }
|
||||||
.join(Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t1.openedUserName }
|
.join (Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t1.openedUserName }
|
||||||
.join(Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t6.userName === t4.userName }
|
.join (Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t6.userName === t4.userName }
|
||||||
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => i asc }
|
.joinLeft(Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t7.userName === t1.assignedUserName}
|
||||||
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => (t1, t5, t2.commentCount, t3, t4, t6) }
|
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => i asc }
|
||||||
|
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => (t1, t5, t2.commentCount, t3, t4, t6, t7) }
|
||||||
.list
|
.list
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,9 +456,8 @@ trait IssuesService {
|
|||||||
def createIssueComment(owner: String, repository: String, commit: CommitInfo)(implicit s: Session): Unit = {
|
def createIssueComment(owner: String, repository: String, commit: CommitInfo)(implicit s: Session): Unit = {
|
||||||
extractIssueId(commit.fullMessage).foreach { issueId =>
|
extractIssueId(commit.fullMessage).foreach { issueId =>
|
||||||
if(getIssue(owner, repository, issueId).isDefined){
|
if(getIssue(owner, repository, issueId).isDefined){
|
||||||
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
|
val userName = getAccountByMailAddress(commit.committerEmailAddress).map(_.userName).getOrElse(commit.committerName)
|
||||||
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
|
createComment(owner, repository, userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,12 +46,17 @@ trait ProtectedBranchService {
|
|||||||
|
|
||||||
object ProtectedBranchService {
|
object ProtectedBranchService {
|
||||||
|
|
||||||
class ProtectedBranchReceiveHook extends ReceiveHook with ProtectedBranchService {
|
class ProtectedBranchReceiveHook extends ReceiveHook with ProtectedBranchService with RepositoryService with AccountService {
|
||||||
override def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
override def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||||
(implicit session: Session): Option[String] = {
|
(implicit session: Session): Option[String] = {
|
||||||
val branch = command.getRefName.stripPrefix("refs/heads/")
|
val branch = command.getRefName.stripPrefix("refs/heads/")
|
||||||
if(branch != command.getRefName){
|
if(branch != command.getRefName){
|
||||||
getProtectedBranchInfo(owner, repository, branch).getStopReason(receivePack.isAllowNonFastForwards, command, pusher)
|
val repositoryInfo = getRepository(owner, repository)
|
||||||
|
if(command.getType == ReceiveCommand.Type.DELETE && repositoryInfo.exists(_.repository.defaultBranch == branch)){
|
||||||
|
Some(s"refusing to delete the branch: ${command.getRefName}.")
|
||||||
|
} else {
|
||||||
|
getProtectedBranchInfo(owner, repository, branch).getStopReason(receivePack.isAllowNonFastForwards, command, pusher)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
|||||||
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true, false)
|
||||||
|
|
||||||
(commits, diffs)
|
(commits, diffs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,6 +413,15 @@ trait RepositoryService { self: AccountService =>
|
|||||||
q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1)
|
q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def hasOwnerRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||||
|
loginAccount match {
|
||||||
|
case Some(a) if(a.isAdmin) => true
|
||||||
|
case Some(a) if(a.userName == owner) => true
|
||||||
|
case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => true
|
||||||
|
case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName)) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def hasDeveloperRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
def hasDeveloperRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||||
loginAccount match {
|
loginAccount match {
|
||||||
|
|||||||
@@ -140,17 +140,16 @@ object SystemSettingsService {
|
|||||||
ldapAuthentication: Boolean,
|
ldapAuthentication: Boolean,
|
||||||
ldap: Option[Ldap],
|
ldap: Option[Ldap],
|
||||||
skinName: String){
|
skinName: String){
|
||||||
def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
|
|
||||||
|
|
||||||
def sshAddress:Option[SshAddress] =
|
def baseUrl(request: HttpServletRequest): String = baseUrl.fold {
|
||||||
for {
|
val url = request.getRequestURL.toString
|
||||||
host <- sshHost if ssh
|
val len = url.length - (request.getRequestURI.length - request.getContextPath.length)
|
||||||
}
|
url.substring(0, len).stripSuffix("/")
|
||||||
yield SshAddress(
|
} (_.stripSuffix("/"))
|
||||||
host,
|
|
||||||
sshPort.getOrElse(DefaultSshPort),
|
def sshAddress:Option[SshAddress] = sshHost.collect { case host if ssh =>
|
||||||
"git"
|
SshAddress(host, sshPort.getOrElse(DefaultSshPort), "git")
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Ldap(
|
case class Ldap(
|
||||||
|
|||||||
@@ -228,16 +228,18 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest){
|
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest){
|
||||||
for{
|
for{
|
||||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
||||||
baseOwner <- users.get(repository.owner)
|
baseOwner <- users.get(repository.owner)
|
||||||
headOwner <- users.get(pullRequest.requestUserName)
|
headOwner <- users.get(pullRequest.requestUserName)
|
||||||
issueUser <- users.get(issue.openedUserName)
|
issueUser <- users.get(issue.openedUserName)
|
||||||
|
assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) }
|
||||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||||
} yield {
|
} yield {
|
||||||
WebHookPullRequestPayload(
|
WebHookPullRequestPayload(
|
||||||
action = action,
|
action = action,
|
||||||
issue = issue,
|
issue = issue,
|
||||||
issueUser = issueUser,
|
issueUser = issueUser,
|
||||||
|
assignee = assignee,
|
||||||
pullRequest = pullRequest,
|
pullRequest = pullRequest,
|
||||||
headRepository = headRepo,
|
headRepository = headRepo,
|
||||||
headOwner = headOwner,
|
headOwner = headOwner,
|
||||||
@@ -273,12 +275,14 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
import WebHookService._
|
import WebHookService._
|
||||||
for{
|
for{
|
||||||
((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch)
|
((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch)
|
||||||
|
assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) }
|
||||||
baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName)
|
baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName)
|
||||||
} yield {
|
} yield {
|
||||||
val payload = WebHookPullRequestPayload(
|
val payload = WebHookPullRequestPayload(
|
||||||
action = action,
|
action = action,
|
||||||
issue = issue,
|
issue = issue,
|
||||||
issueUser = issueUser,
|
issueUser = issueUser,
|
||||||
|
assignee = assignee,
|
||||||
pullRequest = pullRequest,
|
pullRequest = pullRequest,
|
||||||
headRepository = requestRepository,
|
headRepository = requestRepository,
|
||||||
headOwner = headOwner,
|
headOwner = headOwner,
|
||||||
@@ -296,16 +300,17 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
|
|
||||||
trait WebHookPullRequestReviewCommentService extends WebHookService {
|
trait WebHookPullRequestReviewCommentService extends WebHookService {
|
||||||
self: AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService =>
|
self: AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService =>
|
||||||
def callPullRequestReviewCommentWebHook(action: String, comment: CommitComment, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)
|
def callPullRequestReviewCommentWebHook(action: String, comment: CommitComment, repository: RepositoryService.RepositoryInfo,
|
||||||
|
issue: Issue, pullRequest: PullRequest, baseUrl: String, sender: Account)
|
||||||
(implicit s: Session, c: JsonFormat.Context): Unit = {
|
(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||||
import WebHookService._
|
import WebHookService._
|
||||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment){
|
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment){
|
||||||
|
val users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
||||||
for{
|
for{
|
||||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
|
||||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
|
||||||
baseOwner <- users.get(repository.owner)
|
baseOwner <- users.get(repository.owner)
|
||||||
headOwner <- users.get(pullRequest.requestUserName)
|
headOwner <- users.get(pullRequest.requestUserName)
|
||||||
issueUser <- users.get(issue.openedUserName)
|
issueUser <- users.get(issue.openedUserName)
|
||||||
|
assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) }
|
||||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||||
} yield {
|
} yield {
|
||||||
WebHookPullRequestReviewCommentPayload(
|
WebHookPullRequestReviewCommentPayload(
|
||||||
@@ -313,6 +318,7 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
|
|||||||
comment = comment,
|
comment = comment,
|
||||||
issue = issue,
|
issue = issue,
|
||||||
issueUser = issueUser,
|
issueUser = issueUser,
|
||||||
|
assignee = assignee,
|
||||||
pullRequest = pullRequest,
|
pullRequest = pullRequest,
|
||||||
headRepository = headRepo,
|
headRepository = headRepo,
|
||||||
headOwner = headOwner,
|
headOwner = headOwner,
|
||||||
@@ -424,6 +430,7 @@ object WebHookService {
|
|||||||
def apply(action: String,
|
def apply(action: String,
|
||||||
issue: Issue,
|
issue: Issue,
|
||||||
issueUser: Account,
|
issueUser: Account,
|
||||||
|
assignee: Option[Account],
|
||||||
pullRequest: PullRequest,
|
pullRequest: PullRequest,
|
||||||
headRepository: RepositoryInfo,
|
headRepository: RepositoryInfo,
|
||||||
headOwner: Account,
|
headOwner: Account,
|
||||||
@@ -441,6 +448,7 @@ object WebHookService {
|
|||||||
headRepo = headRepoPayload,
|
headRepo = headRepoPayload,
|
||||||
baseRepo = baseRepoPayload,
|
baseRepo = baseRepoPayload,
|
||||||
user = ApiUser(issueUser),
|
user = ApiUser(issueUser),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
mergedComment = mergedComment
|
mergedComment = mergedComment
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -495,6 +503,7 @@ object WebHookService {
|
|||||||
comment: CommitComment,
|
comment: CommitComment,
|
||||||
issue: Issue,
|
issue: Issue,
|
||||||
issueUser: Account,
|
issueUser: Account,
|
||||||
|
assignee: Option[Account],
|
||||||
pullRequest: PullRequest,
|
pullRequest: PullRequest,
|
||||||
headRepository: RepositoryInfo,
|
headRepository: RepositoryInfo,
|
||||||
headOwner: Account,
|
headOwner: Account,
|
||||||
@@ -502,7 +511,7 @@ object WebHookService {
|
|||||||
baseOwner: Account,
|
baseOwner: Account,
|
||||||
sender: Account,
|
sender: Account,
|
||||||
mergedComment: Option[(IssueComment, Account)]
|
mergedComment: Option[(IssueComment, Account)]
|
||||||
) : WebHookPullRequestReviewCommentPayload = {
|
): WebHookPullRequestReviewCommentPayload = {
|
||||||
val headRepoPayload = ApiRepository(headRepository, headOwner)
|
val headRepoPayload = ApiRepository(headRepository, headOwner)
|
||||||
val baseRepoPayload = ApiRepository(baseRepository, baseOwner)
|
val baseRepoPayload = ApiRepository(baseRepository, baseOwner)
|
||||||
val senderPayload = ApiUser(sender)
|
val senderPayload = ApiUser(sender)
|
||||||
@@ -521,6 +530,7 @@ object WebHookService {
|
|||||||
headRepo = headRepoPayload,
|
headRepo = headRepoPayload,
|
||||||
baseRepo = baseRepoPayload,
|
baseRepo = baseRepoPayload,
|
||||||
user = ApiUser(issueUser),
|
user = ApiUser(issueUser),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
mergedComment = mergedComment
|
mergedComment = mergedComment
|
||||||
),
|
),
|
||||||
repository = baseRepoPayload,
|
repository = baseRepoPayload,
|
||||||
|
|||||||
@@ -156,9 +156,13 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
|
|
||||||
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}" }
|
||||||
|
|
||||||
if(!repository.endsWith(".wiki")){
|
if(!repository.endsWith(".wiki")){
|
||||||
defining(request) { implicit r =>
|
defining(request) { implicit r =>
|
||||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl)
|
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
|
||||||
receivePack.setPreReceiveHook(hook)
|
receivePack.setPreReceiveHook(hook)
|
||||||
receivePack.setPostReceiveHook(hook)
|
receivePack.setPostReceiveHook(hook)
|
||||||
}
|
}
|
||||||
@@ -166,7 +170,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
|
|
||||||
if(repository.endsWith(".wiki")){
|
if(repository.endsWith(".wiki")){
|
||||||
defining(request) { implicit r =>
|
defining(request) { implicit r =>
|
||||||
receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl))
|
receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +182,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)
|
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
|
||||||
extends PostReceiveHook with PreReceiveHook
|
extends PostReceiveHook with PreReceiveHook
|
||||||
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService
|
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService
|
||||||
with WebHookPullRequestService with CommitsService {
|
with WebHookPullRequestService with CommitsService {
|
||||||
@@ -219,7 +223,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
val pushedIds = scala.collection.mutable.Set[String]()
|
val pushedIds = scala.collection.mutable.Set[String]()
|
||||||
commands.asScala.foreach { command =>
|
commands.asScala.foreach { command =>
|
||||||
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
||||||
implicit val apiContext = api.JsonFormat.Context(baseUrl)
|
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||||
val refName = command.getRefName.split("/")
|
val refName = command.getRefName.split("/")
|
||||||
val branchName = refName.drop(2).mkString("/")
|
val branchName = refName.drop(2).mkString("/")
|
||||||
val commits = if (refName(1) == "tags") {
|
val commits = if (refName(1) == "tags") {
|
||||||
@@ -320,7 +324,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: String)
|
class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
|
||||||
extends PostReceiveHook with WebHookService with AccountService with RepositoryService {
|
extends PostReceiveHook with WebHookService with AccountService with RepositoryService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook])
|
private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook])
|
||||||
@@ -329,7 +333,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
Database() withTransaction { implicit session =>
|
Database() withTransaction { implicit session =>
|
||||||
try {
|
try {
|
||||||
commands.asScala.headOption.foreach { command =>
|
commands.asScala.headOption.foreach { command =>
|
||||||
implicit val apiContext = api.JsonFormat.Context(baseUrl)
|
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||||
val refName = command.getRefName.split("/")
|
val refName = command.getRefName.split("/")
|
||||||
val commitIds = if (refName(1) == "tags") {
|
val commitIds = if (refName(1) == "tags") {
|
||||||
None
|
None
|
||||||
@@ -347,7 +351,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
diffs._1.collect { case diff if diff.newPath.toLowerCase.endsWith(".md") =>
|
diffs._1.collect { case diff if diff.newPath.toLowerCase.endsWith(".md") =>
|
||||||
val action = if(diff.changeType == ChangeType.ADD) "created" else "edited"
|
val action = if(diff.changeType == ChangeType.ADD) "created" else "edited"
|
||||||
val fileName = diff.newPath
|
val fileName = diff.newPath
|
||||||
println(action + " - " + fileName + " - " + commit.id)
|
//println(action + " - " + fileName + " - " + commit.id)
|
||||||
(action, fileName, commit.id)
|
(action, fileName, commit.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class PluginControllerFilter extends Filter {
|
|||||||
val controller = PluginRegistry().getControllers().filter { case (_, path) =>
|
val controller = PluginRegistry().getControllers().filter { case (_, path) =>
|
||||||
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI
|
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI
|
||||||
val start = path.replaceFirst("/\\*$", "/")
|
val start = path.replaceFirst("/\\*$", "/")
|
||||||
path.endsWith("/*") && (requestUri + "/").startsWith(start)
|
(requestUri + "/").startsWith(start)
|
||||||
}
|
}
|
||||||
|
|
||||||
val filterChainWrapper = controller.foldLeft(chain){ case (chain, (controller, _)) =>
|
val filterChainWrapper = controller.foldLeft(chain){ case (chain, (controller, _)) =>
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ class DefaultGitUploadPack(owner: String, repoName: String) extends DefaultGitCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName)
|
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String]) extends DefaultGitCommand(owner, repoName)
|
||||||
with RepositoryService with AccountService with DeployKeyService {
|
with RepositoryService with AccountService with DeployKeyService {
|
||||||
|
|
||||||
override protected def runTask(authType: AuthType): Unit = {
|
override protected def runTask(authType: AuthType): Unit = {
|
||||||
@@ -169,7 +169,7 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) ex
|
|||||||
val repository = git.getRepository
|
val repository = git.getRepository
|
||||||
val receive = new ReceivePack(repository)
|
val receive = new ReceivePack(repository)
|
||||||
if (!repoName.endsWith(".wiki")) {
|
if (!repoName.endsWith(".wiki")) {
|
||||||
val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl)
|
val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl, sshUrl)
|
||||||
receive.setPreReceiveHook(hook)
|
receive.setPreReceiveHook(hook)
|
||||||
receive.setPostReceiveHook(hook)
|
receive.setPostReceiveHook(hook)
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting) exte
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class GitCommandFactory(baseUrl: String) extends CommandFactory {
|
class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends CommandFactory {
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
||||||
|
|
||||||
override def createCommand(command: String): Command = {
|
override def createCommand(command: String): Command = {
|
||||||
@@ -227,7 +227,7 @@ class GitCommandFactory(baseUrl: String) extends CommandFactory {
|
|||||||
case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, routing(repoName))
|
case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, routing(repoName))
|
||||||
case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, routing(repoName))
|
case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, routing(repoName))
|
||||||
case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName)
|
case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName)
|
||||||
case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl)
|
case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl)
|
||||||
case _ => new UnknownCommand(command)
|
case _ => new UnknownCommand(command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ object SshServer {
|
|||||||
provider.setOverwriteAllowed(false)
|
provider.setOverwriteAllowed(false)
|
||||||
server.setKeyPairProvider(provider)
|
server.setKeyPairProvider(provider)
|
||||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
|
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
|
||||||
server.setCommandFactory(new GitCommandFactory(baseUrl))
|
server.setCommandFactory(new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}")))
|
||||||
server.setShellFactory(new NoShell(sshAddress))
|
server.setShellFactory(new NoShell(sshAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ object FileUtil {
|
|||||||
|
|
||||||
def isImage(name: String): Boolean = getMimeType(name).startsWith("image/")
|
def isImage(name: String): Boolean = getMimeType(name).startsWith("image/")
|
||||||
|
|
||||||
def isUploadableType(name: String): Boolean = mimeTypeWhiteList contains getMimeType(name)
|
|
||||||
|
|
||||||
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
||||||
|
|
||||||
def isText(content: Array[Byte]): Boolean = !content.contains(0)
|
def isText(content: Array[Byte]): Boolean = !content.contains(0)
|
||||||
@@ -53,16 +51,6 @@ object FileUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val mimeTypeWhiteList: Array[String] = Array(
|
|
||||||
"application/pdf",
|
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
"image/gif",
|
|
||||||
"image/jpeg",
|
|
||||||
"image/png",
|
|
||||||
"text/plain")
|
|
||||||
|
|
||||||
def getLfsFilePath(owner: String, repository: String, oid: String): String =
|
def getLfsFilePath(owner: String, repository: String, oid: String): String =
|
||||||
Directory.getLfsDir(owner, repository) + "/" + oid
|
Directory.getLfsDir(owner, repository) + "/" + oid
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ object Implicits {
|
|||||||
// Convert to slick session.
|
// Convert to slick session.
|
||||||
implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request)
|
implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request)
|
||||||
|
|
||||||
implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context = JsonFormat.Context(context.baseUrl)
|
implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context =
|
||||||
|
JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x => s"${x.genericUser}@${x.host}:${x.port}" })
|
||||||
|
|
||||||
implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal {
|
implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal {
|
||||||
|
|
||||||
@@ -77,11 +78,6 @@ object Implicits {
|
|||||||
|
|
||||||
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^" + quote(request.getContextPath) + "/git/", "/")
|
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^" + quote(request.getContextPath) + "/git/", "/")
|
||||||
|
|
||||||
def baseUrl:String = {
|
|
||||||
val url = request.getRequestURL.toString
|
|
||||||
val len = url.length - (request.getRequestURI.length - request.getContextPath.length)
|
|
||||||
url.substring(0, len).stripSuffix("/")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit class RichSession(private val session: HttpSession) extends AnyVal {
|
implicit class RichSession(private val session: HttpSession) extends AnyVal {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
import gitbucket.core.service.RepositoryService
|
import gitbucket.core.service.RepositoryService
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import Directory._
|
import Directory._
|
||||||
@@ -22,6 +24,7 @@ import java.util.function.Consumer
|
|||||||
|
|
||||||
import org.cache2k.{Cache2kBuilder, CacheEntry}
|
import org.cache2k.{Cache2kBuilder, CacheEntry}
|
||||||
import org.eclipse.jgit.api.errors.{InvalidRefNameException, JGitInternalException, NoHeadException, RefAlreadyExistsException}
|
import org.eclipse.jgit.api.errors.{InvalidRefNameException, JGitInternalException, NoHeadException, RefAlreadyExistsException}
|
||||||
|
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter}
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@@ -114,7 +117,8 @@ object JGitUtil {
|
|||||||
newObjectId: Option[String],
|
newObjectId: Option[String],
|
||||||
oldMode: String,
|
oldMode: String,
|
||||||
newMode: String,
|
newMode: String,
|
||||||
tooLarge: Boolean
|
tooLarge: Boolean,
|
||||||
|
patch: Option[String]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -515,9 +519,10 @@ object JGitUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the tuple of diff of the given commit and the previous commit id.
|
* Returns the tuple of diff of the given commit and parent commit ids.
|
||||||
|
* DiffInfos returned from this method don't include the patch property.
|
||||||
*/
|
*/
|
||||||
def getDiffs(git: Git, id: String, fetchContent: Boolean = true): (List[DiffInfo], Option[String]) = {
|
def getDiffs(git: Git, id: String, fetchContent: Boolean): (List[DiffInfo], Option[String]) = {
|
||||||
@scala.annotation.tailrec
|
@scala.annotation.tailrec
|
||||||
def getCommitLog(i: java.util.Iterator[RevCommit], logs: List[RevCommit]): List[RevCommit] =
|
def getCommitLog(i: java.util.Iterator[RevCommit], logs: List[RevCommit]): List[RevCommit] =
|
||||||
i.hasNext match {
|
i.hasNext match {
|
||||||
@@ -538,7 +543,7 @@ object JGitUtil {
|
|||||||
} else {
|
} else {
|
||||||
commits(1)
|
commits(1)
|
||||||
}
|
}
|
||||||
(getDiffs(git, oldCommit.getName, id, fetchContent), Some(oldCommit.getName))
|
(getDiffs(git, oldCommit.getName, id, fetchContent, false), Some(oldCommit.getName))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// initial commit
|
// initial commit
|
||||||
@@ -551,7 +556,7 @@ object JGitUtil {
|
|||||||
buffer.append((if(!fetchContent){
|
buffer.append((if(!fetchContent){
|
||||||
DiffInfo(
|
DiffInfo(
|
||||||
changeType = ChangeType.ADD,
|
changeType = ChangeType.ADD,
|
||||||
oldPath = null,
|
oldPath = "",
|
||||||
newPath = treeWalk.getPathString,
|
newPath = treeWalk.getPathString,
|
||||||
oldContent = None,
|
oldContent = None,
|
||||||
newContent = None,
|
newContent = None,
|
||||||
@@ -561,12 +566,13 @@ object JGitUtil {
|
|||||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||||
oldMode = treeWalk.getFileMode(0).toString,
|
oldMode = treeWalk.getFileMode(0).toString,
|
||||||
newMode = treeWalk.getFileMode(0).toString,
|
newMode = treeWalk.getFileMode(0).toString,
|
||||||
tooLarge = false
|
tooLarge = false,
|
||||||
|
patch = None
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DiffInfo(
|
DiffInfo(
|
||||||
changeType = ChangeType.ADD,
|
changeType = ChangeType.ADD,
|
||||||
oldPath = null,
|
oldPath = "",
|
||||||
newPath = treeWalk.getPathString,
|
newPath = treeWalk.getPathString,
|
||||||
oldContent = None,
|
oldContent = None,
|
||||||
newContent = JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray),
|
newContent = JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||||
@@ -576,7 +582,8 @@ object JGitUtil {
|
|||||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||||
oldMode = treeWalk.getFileMode(0).toString,
|
oldMode = treeWalk.getFileMode(0).toString,
|
||||||
newMode = treeWalk.getFileMode(0).toString,
|
newMode = treeWalk.getFileMode(0).toString,
|
||||||
tooLarge = false
|
tooLarge = false,
|
||||||
|
patch = None
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -586,7 +593,7 @@ object JGitUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getDiffs(git: Git, from: String, to: String, fetchContent: Boolean): List[DiffInfo] = {
|
def getDiffs(git: Git, from: String, to: String, fetchContent: Boolean, makePatch: Boolean): List[DiffInfo] = {
|
||||||
val reader = git.getRepository.newObjectReader
|
val reader = git.getRepository.newObjectReader
|
||||||
val oldTreeIter = new CanonicalTreeParser
|
val oldTreeIter = new CanonicalTreeParser
|
||||||
oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}"))
|
oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}"))
|
||||||
@@ -612,7 +619,8 @@ object JGitUtil {
|
|||||||
newObjectId = Option(diff.getNewId).map(_.name),
|
newObjectId = Option(diff.getNewId).map(_.name),
|
||||||
oldMode = diff.getOldMode.toString,
|
oldMode = diff.getOldMode.toString,
|
||||||
newMode = diff.getNewMode.toString,
|
newMode = diff.getNewMode.toString,
|
||||||
tooLarge = true
|
tooLarge = true,
|
||||||
|
patch = None
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val oldIsImage = FileUtil.isImage(diff.getOldPath)
|
val oldIsImage = FileUtil.isImage(diff.getOldPath)
|
||||||
@@ -630,7 +638,8 @@ object JGitUtil {
|
|||||||
newObjectId = Option(diff.getNewId).map(_.name),
|
newObjectId = Option(diff.getNewId).map(_.name),
|
||||||
oldMode = diff.getOldMode.toString,
|
oldMode = diff.getOldMode.toString,
|
||||||
newMode = diff.getNewMode.toString,
|
newMode = diff.getNewMode.toString,
|
||||||
tooLarge = false
|
tooLarge = false,
|
||||||
|
patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DiffInfo(
|
DiffInfo(
|
||||||
@@ -645,13 +654,23 @@ object JGitUtil {
|
|||||||
newObjectId = Option(diff.getNewId).map(_.name),
|
newObjectId = Option(diff.getNewId).map(_.name),
|
||||||
oldMode = diff.getOldMode.toString,
|
oldMode = diff.getOldMode.toString,
|
||||||
newMode = diff.getNewMode.toString,
|
newMode = diff.getNewMode.toString,
|
||||||
tooLarge = false
|
tooLarge = false,
|
||||||
|
patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.toList
|
}.toList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def makePatchFromDiffEntry(git: Git, diff: DiffEntry): String = {
|
||||||
|
val out = new ByteArrayOutputStream()
|
||||||
|
using(new DiffFormatter(out)){ formatter =>
|
||||||
|
formatter.setRepository(git.getRepository)
|
||||||
|
formatter.format(diff)
|
||||||
|
val patch = new String(out.toByteArray) // TODO charset???
|
||||||
|
patch.split("\n").drop(4).mkString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of branch names of the specified commit.
|
* Returns the list of branch names of the specified commit.
|
||||||
|
|||||||
68
src/main/scala/gitbucket/core/util/Mailer.scala
Normal file
68
src/main/scala/gitbucket/core/util/Mailer.scala
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
import gitbucket.core.service.SystemSettingsService
|
||||||
|
|
||||||
|
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
|
||||||
|
import SystemSettingsService.SystemSettings
|
||||||
|
|
||||||
|
class Mailer(settings: SystemSettings){
|
||||||
|
|
||||||
|
def send(to: String, subject: String, textMsg: String, htmlMsg: Option[String] = None, loginAccount: Option[Account] = None): Unit = {
|
||||||
|
createMail(subject, textMsg, htmlMsg, loginAccount).foreach { email =>
|
||||||
|
email.addTo(to).send
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def sendBcc(bcc: Seq[String], subject: String, textMsg: String, htmlMsg: Option[String] = None, loginAccount: Option[Account] = None): Unit = {
|
||||||
|
createMail(subject, textMsg, htmlMsg, loginAccount).foreach { email =>
|
||||||
|
bcc.foreach { address =>
|
||||||
|
email.addBcc(address)
|
||||||
|
}
|
||||||
|
email.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def createMail(subject: String, textMsg: String, htmlMsg: Option[String] = None, loginAccount: Option[Account] = None): Option[HtmlEmail] = {
|
||||||
|
if(settings.notification == true){
|
||||||
|
settings.smtp.map { smtp =>
|
||||||
|
val email = new HtmlEmail
|
||||||
|
email.setHostName(smtp.host)
|
||||||
|
email.setSmtpPort(smtp.port.get)
|
||||||
|
smtp.user.foreach { user =>
|
||||||
|
email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
|
||||||
|
}
|
||||||
|
smtp.ssl.foreach { ssl =>
|
||||||
|
email.setSSLOnConnect(ssl)
|
||||||
|
if(ssl == true) {
|
||||||
|
email.setSslSmtpPort(smtp.port.get.toString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smtp.starttls.foreach { starttls =>
|
||||||
|
email.setStartTLSEnabled(starttls)
|
||||||
|
email.setStartTLSRequired(starttls)
|
||||||
|
}
|
||||||
|
smtp.fromAddress
|
||||||
|
.map (_ -> smtp.fromName.getOrElse(loginAccount.map(_.userName).getOrElse("GitBucket")))
|
||||||
|
.orElse (Some("notifications@gitbucket.com" -> loginAccount.map(_.userName).getOrElse("GitBucket")))
|
||||||
|
.foreach { case (address, name) =>
|
||||||
|
email.setFrom(address, name)
|
||||||
|
}
|
||||||
|
email.setCharset("UTF-8")
|
||||||
|
email.setSubject(subject)
|
||||||
|
email.setTextMsg(textMsg)
|
||||||
|
htmlMsg.foreach { msg =>
|
||||||
|
email.setHtmlMsg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
email
|
||||||
|
}
|
||||||
|
} else None
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//class MockMailer extends Notifier {
|
||||||
|
// def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
|
||||||
|
// (recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
|
||||||
|
//}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package gitbucket.core.util
|
|
||||||
|
|
||||||
import gitbucket.core.model.{Session, Account}
|
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
|
||||||
import gitbucket.core.servlet.Database
|
|
||||||
|
|
||||||
import scala.concurrent._
|
|
||||||
import scala.util.{Success, Failure}
|
|
||||||
import ExecutionContext.Implicits.global
|
|
||||||
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import gitbucket.core.controller.Context
|
|
||||||
import SystemSettingsService.Smtp
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The trait for notifications.
|
|
||||||
* This is used by notifications plugin, which provides notifications feature on GitBucket.
|
|
||||||
* Please see the plugin for details.
|
|
||||||
*/
|
|
||||||
trait Notifier {
|
|
||||||
def toNotify(subject: String, textMsg: String)
|
|
||||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
|
|
||||||
toNotify(subject, textMsg, None)(recipients)
|
|
||||||
}
|
|
||||||
|
|
||||||
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String])
|
|
||||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Notifier {
|
|
||||||
def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match {
|
|
||||||
case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get)
|
|
||||||
case _ => new MockMailer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Mailer(private val smtp: Smtp) extends Notifier {
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[Mailer])
|
|
||||||
|
|
||||||
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
|
|
||||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
|
|
||||||
context.loginAccount.foreach { loginAccount =>
|
|
||||||
val database = Database()
|
|
||||||
|
|
||||||
val f = Future {
|
|
||||||
database withSession { session =>
|
|
||||||
recipients(loginAccount)(session) foreach { to =>
|
|
||||||
send(to, subject, loginAccount, textMsg, htmlMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"Notifications Successful."
|
|
||||||
}
|
|
||||||
f.onComplete {
|
|
||||||
case Success(s) => logger.debug(s)
|
|
||||||
case Failure(t) => logger.error("Notifications Failed.", t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def send(to: String, subject: String, loginAccount: Account, textMsg: String, htmlMsg: Option[String] = None): Unit = {
|
|
||||||
val email = new HtmlEmail
|
|
||||||
email.setHostName(smtp.host)
|
|
||||||
email.setSmtpPort(smtp.port.get)
|
|
||||||
smtp.user.foreach { user =>
|
|
||||||
email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
|
|
||||||
}
|
|
||||||
smtp.ssl.foreach { ssl =>
|
|
||||||
email.setSSLOnConnect(ssl)
|
|
||||||
if(ssl == true) {
|
|
||||||
email.setSslSmtpPort(smtp.port.get.toString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
smtp.starttls.foreach { starttls =>
|
|
||||||
email.setStartTLSEnabled(starttls)
|
|
||||||
email.setStartTLSRequired(starttls)
|
|
||||||
}
|
|
||||||
smtp.fromAddress
|
|
||||||
.map (_ -> smtp.fromName.getOrElse(loginAccount.userName))
|
|
||||||
.orElse (Some("notifications@gitbucket.com" -> loginAccount.userName))
|
|
||||||
.foreach { case (address, name) =>
|
|
||||||
email.setFrom(address, name)
|
|
||||||
}
|
|
||||||
email.setCharset("UTF-8")
|
|
||||||
email.setSubject(subject)
|
|
||||||
email.setTextMsg(textMsg)
|
|
||||||
htmlMsg.foreach { msg =>
|
|
||||||
email.setHtmlMsg(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
email.addTo(to).send
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
class MockMailer extends Notifier {
|
|
||||||
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
|
|
||||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
|
|
||||||
}
|
|
||||||
@@ -65,8 +65,6 @@ $(function(){
|
|||||||
url: '@context.path/upload/file/@repository.owner/@repository.name',
|
url: '@context.path/upload/file/@repository.owner/@repository.name',
|
||||||
maxFilesize: 10,
|
maxFilesize: 10,
|
||||||
clickable: @clickable,
|
clickable: @clickable,
|
||||||
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
|
|
||||||
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
|
|
||||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||||
success: function(file, id) {
|
success: function(file, id) {
|
||||||
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) +
|
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) +
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
@defining(label.map(_.labelId).getOrElse("new")){ labelId =>
|
@defining(label.map(_.labelId).getOrElse("new")){ labelId =>
|
||||||
<div id="edit-label-area-@labelId">
|
<div id="edit-label-area-@labelId">
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<input type="text" id="labelName-@labelId" style="width: 300px; float: left; margin-right: 4px;" class="form-control" value="@label.map(_.labelName)"@if(labelId == "new"){ placeholder="New label name"}/>
|
<input type="text" id="labelName-@labelId" style="width: 300px; float: left; margin-right: 4px;" class="form-control input-sm" value="@label.map(_.labelName)"@if(labelId == "new"){ placeholder="New label name"}/>
|
||||||
<div id="label-color-@labelId" class="input-group color bscp" data-color="#@label.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; float: left;">
|
<div id="label-color-@labelId" class="input-group color bscp" data-color="#@label.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; float: left;">
|
||||||
<input type="text" class="form-control" id="labelColor-@labelId" value="#@label.map(_.color).getOrElse("888888")" style="width: 100px;">
|
<input type="text" class="form-control input-sm" id="labelColor-@labelId" value="#@label.map(_.color).getOrElse("888888")" style="width: 100px;">
|
||||||
<span class="input-group-addon"><i style="background-color: #@label.map(_.color).getOrElse("888888");"></i></span>
|
<span class="input-group-addon"><i style="background-color: #@label.map(_.color).getOrElse("888888");"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
</script>
|
</script>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
<span id="label-error-@labelId" class="error"></span>
|
<span id="label-error-@labelId" class="error"></span>
|
||||||
<input type="button" id="cancel-@labelId" class="btn btn-default label-edit-cancel" value="Cancel">
|
<input type="button" id="cancel-@labelId" class="btn btn-sm btn-default label-edit-cancel" value="Cancel">
|
||||||
<input type="button" id="submit-@labelId" class="btn btn-success" style="margin-bottom: 0px;" value="@(if(labelId == "new") "Create label" else "Save changes")"/>
|
<input type="button" id="submit-@labelId" class="btn btn-sm btn-success" style="margin-bottom: 0px;" value="@(if(labelId == "new") "Create label" else "Save changes")"/>
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||||
<div class="milestone row" id="label-@label.labelId">
|
<div class="milestone row" id="label-@label.labelId">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div style="margin-top: 6px">
|
<div>
|
||||||
<a href="@helpers.url(repository)/issues?labels=@helpers.urlEncode(label.labelName)" id="label-row-content-@label.labelId">
|
<a href="@helpers.url(repository)/issues?labels=@helpers.urlEncode(label.labelName)" id="label-row-content-@label.labelId">
|
||||||
<span style="background-color: #@label.color; color: #@label.fontColor; padding: 8px; font-size: 120%; border-radius: 4px;">
|
<span style="background-color: #@label.color; color: #@label.fontColor; padding: 8px; border-radius: 4px;">
|
||||||
<i class="octicon octicon-tag" style="color: #@label.fontColor;"></i>
|
<i class="octicon octicon-tag" style="color: #@label.fontColor;"></i>
|
||||||
@label.labelName
|
@label.labelName
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -16,9 +16,7 @@
|
|||||||
<table class="table table-bordered table-hover table-issues">
|
<table class="table table-bordered table-hover table-issues">
|
||||||
<thead>
|
<thead>
|
||||||
<tr id="label-row-header">
|
<tr id="label-row-header">
|
||||||
<th style="background-color: #eee;">
|
<th>@labels.size labels</th>
|
||||||
<span class="small">@labels.size labels</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -13,15 +13,13 @@
|
|||||||
<table class="table table-bordered table-hover table-issues">
|
<table class="table table-bordered table-hover table-issues">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="background-color: #eee;">
|
<th>
|
||||||
<span class="small">
|
<a class="button-link@if(state == "open"){ selected}" href="?state=open">
|
||||||
<a class="button-link@if(state == "open"){ selected}" href="?state=open">
|
@milestones.count(_._1.closedDate.isEmpty) Open
|
||||||
@milestones.count(_._1.closedDate.isEmpty) Open
|
</a>
|
||||||
</a>
|
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
|
||||||
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
|
@milestones.count(_._1.closedDate.isDefined) Closed
|
||||||
@milestones.count(_._1.closedDate.isDefined) Closed
|
</a>
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
@defining(priority.map(_.priorityId).getOrElse("new")){ priorityId =>
|
@defining(priority.map(_.priorityId).getOrElse("new")){ priorityId =>
|
||||||
<div id="edit-priority-area-@priorityId">
|
<div id="edit-priority-area-@priorityId">
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<input type="text" id="priorityName-@priorityId" style="width: 300px; float: left; margin-right: 4px;" class="form-control" value="@priority.map(_.priorityName)"@if(priorityId == "new"){ placeholder="New priority name"}/>
|
<input type="text" id="priorityName-@priorityId" style="width: 200px; float: left; margin-right: 4px;" class="form-control input-sm" value="@priority.map(_.priorityName)"@if(priorityId == "new"){ placeholder="New priority name"}/>
|
||||||
<div id="priority-color-@priorityId" class="input-group color bscp" data-color="#@priority.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; float: left;">
|
<div id="priority-color-@priorityId" class="input-group color bscp" data-color="#@priority.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; float: left;">
|
||||||
<input type="text" class="form-control" id="priorityColor-@priorityId" value="#@priority.map(_.color).getOrElse("888888")" style="width: 100px;">
|
<input type="text" class="form-control input-sm" id="priorityColor-@priorityId" value="#@priority.map(_.color).getOrElse("888888")" style="width: 100px;">
|
||||||
<span class="input-group-addon"><i style="background-color: #@priority.map(_.color).getOrElse("888888");"></i></span>
|
<span class="input-group-addon"><i style="background-color: #@priority.map(_.color).getOrElse("888888");"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$('div#priority-color-@priorityId').colorpicker({format: "hex"});
|
$('div#priority-color-@priorityId').colorpicker({format: "hex"});
|
||||||
</script>
|
</script>
|
||||||
<input type="text" id="description-@priorityId" style="width: 500px; float: left; margin-left: 4px;" class="form-control" value="@priority.flatMap(_.description).getOrElse("")" placeholder="Description..." />
|
<input type="text" id="description-@priorityId" style="width: 500px; float: left; margin-left: 4px;" class="form-control input-sm" value="@priority.flatMap(_.description).getOrElse("")" placeholder="Description..." />
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
<span id="priority-error-@priorityId" class="error"></span>
|
<span id="priority-error-@priorityId" class="error"></span>
|
||||||
<input type="button" id="cancel-@priorityId" class="btn btn-default priority-edit-cancel" value="Cancel">
|
<input type="button" id="cancel-@priorityId" class="btn btn-sm btn-default priority-edit-cancel" value="Cancel">
|
||||||
<input type="button" id="submit-@priorityId" class="btn btn-success" style="margin-bottom: 0px;" value="@(if(priorityId == "new") "Create priority" else "Save changes")"/>
|
<input type="button" id="submit-@priorityId" class="btn btn-sm btn-success" style="margin-bottom: 0px;" value="@(if(priorityId == "new") "Create priority" else "Save changes")"/>
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,9 +16,7 @@
|
|||||||
<table id="priorities-table" class="table table-bordered table-hover table-issues">
|
<table id="priorities-table" class="table table-bordered table-hover table-issues">
|
||||||
<thead>
|
<thead>
|
||||||
<tr id="priority-row-header">
|
<tr id="priority-row-header">
|
||||||
<th style="background-color: #eee;">
|
<th>@priorities.size priorities</th>
|
||||||
<span class="small">@priorities.size priorities</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
@if(hasWritePermission) {
|
@if(hasWritePermission) {
|
||||||
<div class="pull-left priority-sort-handle" style="margin-top: 3px"><i class="octicon octicon-grabber"></i></div>
|
<div class="pull-left priority-sort-handle" style="margin-top: 3px"><i class="octicon octicon-grabber"></i></div>
|
||||||
}
|
}
|
||||||
<div style="margin-top: 6px">
|
<div>
|
||||||
<a href="@helpers.url(repository)/issues?priority=@helpers.urlEncode(priority.priorityName)&state=open" id="priority-row-content-@priority.priorityId">
|
<a href="@helpers.url(repository)/issues?priority=@helpers.urlEncode(priority.priorityName)&state=open" id="priority-row-content-@priority.priorityId">
|
||||||
<span style="background-color: #@priority.color; color: #@priority.fontColor; padding: 8px; font-size: 120%; border-radius: 4px;">
|
<span style="background-color: #@priority.color; color: #@priority.fontColor; padding: 8px; border-radius: 4px;">
|
||||||
<i class="octicon octicon-flame" style="color: #@priority.fontColor;"></i> @priority.priorityName
|
<i class="octicon octicon-flame" style="color: #@priority.fontColor;"></i> @priority.priorityName
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
|
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
|
||||||
}
|
}
|
||||||
@if(context.loginAccount.isDefined && (context.loginAccount.get.isAdmin || repository.managers.contains(context.loginAccount.get.userName))){
|
@if(context.loginAccount.isDefined && (context.loginAccount.get.isAdmin || repository.managers.contains(context.loginAccount.get.userName))){
|
||||||
@menuitem("/settings", "settings", "Settings", "tools")
|
@menuitem("/settings", "settings", "Settings", "gear")
|
||||||
}
|
}
|
||||||
@gitbucket.core.plugin.PluginRegistry().getRepositoryMenus.map { menu =>
|
@gitbucket.core.plugin.PluginRegistry().getRepositoryMenus.map { menu =>
|
||||||
@menu(repository, context).map { link =>
|
@menu(repository, context).map { link =>
|
||||||
@@ -88,9 +88,6 @@
|
|||||||
forked from <a href="@context.path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
|
forked from <a href="@context.path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@x.description.map { description =>
|
|
||||||
<div class="normal muted" style="margin-left: 36px; font-size: 80%;">@Html(helpers.detectAndRenderLinks(description, repository))</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="show-title pull-right">
|
<div class="show-title pull-right">
|
||||||
@if(isManageable || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
@if(isManageable || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||||
<a class="btn" href="#" id="edit">Edit</a>
|
<a class="btn btn-default" href="#" id="edit">Edit</a>
|
||||||
}
|
}
|
||||||
@if(context.loginAccount.isDefined){
|
@if(context.loginAccount.isDefined){
|
||||||
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
|
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
@gitbucket.core.html.menu("files", repository){
|
@gitbucket.core.html.menu("files", repository){
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<div class="pull-right hide-if-blame"><div class="btn-group">
|
<div class="pull-right hide-if-blame"><div class="btn-group">
|
||||||
|
<a href="@helpers.url(repository)/blob/@latestCommit.id/@pathList.mkString("/")" data-hotkey="y" style="display: none;">Transfer to URL with SHA</a>
|
||||||
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t">Find file</a>
|
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t">Find file</a>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="line-age-legend">
|
<div class="line-age-legend">
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
</ol>
|
</ol>
|
||||||
<span>Older</span>
|
<span>Older</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="branchCtrlWrapper" style="display:inline;">
|
||||||
@gitbucket.core.helper.html.branchcontrol(
|
@gitbucket.core.helper.html.branchcontrol(
|
||||||
branch,
|
branch,
|
||||||
repository,
|
repository,
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
<li><a href="@helpers.url(repository)/blob/@helpers.encodeRefName(x)/@pathList.mkString("/")">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
<li><a href="@helpers.url(repository)/blob/@helpers.encodeRefName(x)/@pathList.mkString("/")">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)">@repository.name</a> /
|
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)">@repository.name</a> /
|
||||||
@pathList.zipWithIndex.map { case (section, i) =>
|
@pathList.zipWithIndex.map { case (section, i) =>
|
||||||
@if(i == pathList.length - 1){
|
@if(i == pathList.length - 1){
|
||||||
@@ -130,14 +133,18 @@ $(window).load(function(){
|
|||||||
}
|
}
|
||||||
var line = pos[i].id.replace(/^L/,'');
|
var line = pos[i].id.replace(/^L/,'');
|
||||||
var hash = location.hash;
|
var hash = location.hash;
|
||||||
|
var commitUrl = '@helpers.url(repository)/blob/@latestCommit.id/@pathList.mkString("/")';
|
||||||
if(e.shiftKey == true && hash.match(/#L\d+(-L\d+)?/)){
|
if(e.shiftKey == true && hash.match(/#L\d+(-L\d+)?/)){
|
||||||
var lines = hash.split('-');
|
var lines = hash.split('-');
|
||||||
location.hash = lines[0] + '-L' + line;
|
window.history.pushState('', '', commitUrl + lines[0] + '-L' + line);
|
||||||
} else {
|
} else {
|
||||||
var p = $("#L"+line).attr('id',"");
|
var p = $("#L"+line).attr('id',"");
|
||||||
location.hash = '#L' + line;
|
window.history.pushState('', '', commitUrl + '#L' + line);
|
||||||
p.attr('id','L'+line);
|
p.attr('id','L'+line);
|
||||||
}
|
}
|
||||||
|
$("#branchCtrlWrapper .btn .muted").text("tree:");
|
||||||
|
$("#branchCtrlWrapper .btn .strong").text("@latestCommit.id.substring(0, 10)");
|
||||||
|
updateHighlighting();
|
||||||
}).appendTo(pre);
|
}).appendTo(pre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<table class="table table-hover branches">
|
<table class="table table-hover branches">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="background: #f5f5f5;color: #666;" colspan="3">All branches</th>
|
<th colspan="3">All branches</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="branch-details">
|
<td class="branch-details">
|
||||||
@if(isProtected){ <span class="octicon octicon-shield" title="This branch is protected"></span> }
|
@if(isProtected){ <span class="octicon octicon-shield" title="This branch is protected"></span> }
|
||||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch.name)" class="branch-name">@branch.name</a>
|
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch.name)">@branch.name</a>
|
||||||
<span class="branch-meta">
|
<span class="branch-meta">
|
||||||
<span>Updated @gitbucket.core.helper.html.datetimeago(branch.commitTime, false)
|
<span>Updated @gitbucket.core.helper.html.datetimeago(branch.commitTime, false)
|
||||||
by <span>@helpers.user(branch.committerName, branch.committerEmailAddress, "muted-link")</span>
|
by <span>@helpers.user(branch.committerName, branch.committerEmailAddress, "muted-link")</span>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="text-right">
|
||||||
<div class="branch-action">
|
<div class="branch-action">
|
||||||
@if(repository.repository.defaultBranch != branch.name){
|
@if(repository.repository.defaultBranch != branch.name){
|
||||||
@branch.mergeInfo.map{ info =>
|
@branch.mergeInfo.map{ info =>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="box-header">
|
<th class="box-header">
|
||||||
<div class="pull-right align-right">
|
<div class="pull-right align-right">
|
||||||
<a href="@helpers.url(repository)/tree/@commit.id" class="btn btn-small">Browse code</a>
|
<a href="@helpers.url(repository)/tree/@commit.id" class="btn btn-default">Browse code</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="commit-log">@helpers.link(commit.summary, repository)</div>
|
<div class="commit-log">@helpers.link(commit.summary, repository)</div>
|
||||||
@if(commit.description.isDefined){
|
@if(commit.description.isDefined){
|
||||||
|
|||||||
@@ -22,9 +22,17 @@
|
|||||||
s"${(repository.name :: pathList).mkString("/")} at ${helpers.encodeRefName(branch)} - ${repository.owner}/${repository.name}"
|
s"${(repository.name :: pathList).mkString("/")} at ${helpers.encodeRefName(branch)} - ${repository.owner}/${repository.name}"
|
||||||
}, Some(repository)) {
|
}, Some(repository)) {
|
||||||
@gitbucket.core.html.menu("files", repository, Some(branch), info, error){
|
@gitbucket.core.html.menu("files", repository, Some(branch), info, error){
|
||||||
|
@if(pathList.isEmpty) {
|
||||||
|
@repository.repository.description.map { description =>
|
||||||
|
<p class="pc" style="margin-bottom: 10px;">
|
||||||
|
<span class="normal muted">@Html(helpers.detectAndRenderLinks(description, repository))</span>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
<div class="head" style="height: 24px;">
|
<div class="head" style="height: 24px;">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
|
<a href="@helpers.url(repository)/tree/@latestCommit.id/@pathList.mkString("/")" data-hotkey="y" style="display: none;">Transfer to URL with SHA</a>
|
||||||
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t"><i class="octicon octicon-search"></i></a>
|
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t"><i class="octicon octicon-search"></i></a>
|
||||||
<a href="@helpers.url(repository)/commits/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i> @if(commitCount > 10000){10000+} else {@commitCount} @helpers.plural(commitCount, "commit")</a>
|
<a href="@helpers.url(repository)/commits/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i> @if(commitCount > 10000){10000+} else {@commitCount} @helpers.plural(commitCount, "commit")</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,8 +110,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th colspan="4" class="latest-commit">
|
<th colspan="4" class="latest-commit">
|
||||||
<div>
|
<div>
|
||||||
<div class="pull-right align-right monospace" style="line-height: 18px;">
|
<div class="pull-right align-right" style="line-height: 18px;">
|
||||||
<a href="@helpers.url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> @latestCommit.id.substring(0, 10)</a>
|
<a href="@helpers.url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> <span class="monospace">@latestCommit.id.substring(0, 10)</span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="author-info">
|
<div class="author-info">
|
||||||
<div class="author">
|
<div class="author">
|
||||||
@@ -145,21 +153,35 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="ellipsis-cell" style="width: 20%; min-width: 160px;">
|
<td class="ellipsis-cell" style="width: 20%; min-width: 160px;">
|
||||||
@if(file.isDirectory){
|
@if(file.isDirectory){
|
||||||
@if(file.linkUrl.isDefined){
|
@{file.linkUrl match {
|
||||||
<a href="@file.linkUrl">
|
case Some(linkUrl) if linkUrl.startsWith("http://") || linkUrl.startsWith("https://") => {
|
||||||
<span class="simplified-path">@file.name.split("/").toList.init match {
|
<a href={linkUrl}>
|
||||||
case Nil => {}
|
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||||
case list => {@list.mkString("", "/", "/")}
|
case Nil => ""
|
||||||
}</span>@file.name.split("/").toList.last
|
case list => list.mkString("", "/", "/")
|
||||||
</a>
|
}}</span>
|
||||||
} else {
|
{file.name.split("/").toList.last}
|
||||||
<a href="@helpers.url(repository)/tree@{(branch :: pathList).map(helpers.encodeRefName).mkString("/", "/", "/")}@{helpers.encodeRefName(file.name)}">
|
</a>
|
||||||
<span class="simplified-path">@file.name.split("/").toList.init match {
|
}
|
||||||
case Nil => {}
|
case Some(_) => {
|
||||||
case list => {@list.mkString("", "/", "/")}
|
<span>
|
||||||
}</span>@file.name.split("/").toList.last
|
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||||
</a>
|
case Nil => ""
|
||||||
}
|
case list => list.mkString("", "/", "/")
|
||||||
|
}}</span>
|
||||||
|
{file.name.split("/").toList.last}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
case None => {
|
||||||
|
<a href={helpers.url(repository) + "/tree" + (branch :: pathList).map(helpers.encodeRefName).mkString("/", "/", "/") + helpers.encodeRefName(file.name)}>
|
||||||
|
<span class="simplified-path">{file.name.split("/").toList.init match {
|
||||||
|
case Nil => ""
|
||||||
|
case list => list.mkString("", "/", "/")
|
||||||
|
}}</span>
|
||||||
|
{file.name.split("/").toList.last}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}}
|
||||||
} else {
|
} else {
|
||||||
<a href="@helpers.url(repository)/blob@{(branch :: pathList).map(helpers.encodeRefName).mkString("/", "/", "/")}@{helpers.encodeRefName(file.name)}">@file.name</a>
|
<a href="@helpers.url(repository)/blob@{(branch :: pathList).map(helpers.encodeRefName).mkString("/", "/", "/")}@{helpers.encodeRefName(file.name)}">@file.name</a>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
<span id="error-password" class="error"></span>
|
<span id="error-password" class="error"></span>
|
||||||
<input type="password" name="password" id="password" class="form-control" value="@password"/>
|
<input type="password" name="password" id="password" class="form-control" value="@password"/>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" class="btn btn-success" value="Sign in"/>
|
<input type="hidden" name="hash"/>
|
||||||
|
<input type="submit" class="btn btn-success" value="Sign in" onClick="this.form.hash.value = window.location.hash;"/>
|
||||||
@if(systemSettings.allowAccountRegistration){
|
@if(systemSettings.allowAccountRegistration){
|
||||||
or <a href="@context.path/register">Create new account</a>
|
or <a href="@context.path/register">Create new account</a>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ $(function(){
|
|||||||
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
|
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
|
||||||
maxFilesize: 10,
|
maxFilesize: 10,
|
||||||
clickable: false,
|
clickable: false,
|
||||||
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
|
|
||||||
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
|
|
||||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||||
success: function(file, id) {
|
success: function(file, id) {
|
||||||
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
|
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
|
||||||
@@ -62,8 +60,6 @@ $(function(){
|
|||||||
$('.clickable').dropzone({
|
$('.clickable').dropzone({
|
||||||
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
|
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
|
||||||
maxFilesize: 10,
|
maxFilesize: 10,
|
||||||
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
|
|
||||||
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
|
|
||||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||||
success: function(file, id) {
|
success: function(file, id) {
|
||||||
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
|
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
|
||||||
@@ -78,7 +74,7 @@ $(function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('#delete').click(function(){
|
$('#delete').click(function(){
|
||||||
return confirm('Are you sure you want to delete this page?');
|
return confirm('Are you sure you want to delete this page?');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -392,8 +392,8 @@ a.btn-danger:hover .octicon {
|
|||||||
/* Head Menu */
|
/* Head Menu */
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
div.headbar {
|
div.headbar {
|
||||||
padding-top: 19px;
|
padding-top: 4px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
@@ -438,8 +438,9 @@ div.repository-content {
|
|||||||
margin-left: 40px;
|
margin-left: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.branches>thead>tr>th, table.branches>tbody>tr>td{
|
table.branches>tbody>tr>td{
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.branches .muted-link{
|
.branches .muted-link{
|
||||||
@@ -450,15 +451,6 @@ table.branches>thead>tr>th, table.branches>tbody>tr>td{
|
|||||||
color: #4183c4;
|
color: #4183c4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.branches .branch-name{
|
|
||||||
color: #4183c4;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 6px;
|
|
||||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
||||||
background-color: rgba(209,227,237,0.5);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.branches .branch-meta{
|
.branches .branch-meta{
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class JsonFormatSpec extends FunSuite {
|
|||||||
}
|
}
|
||||||
val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||||
val repo1Name = RepositoryName("octocat/Hello-World")
|
val repo1Name = RepositoryName("octocat/Hello-World")
|
||||||
implicit val context = JsonFormat.Context("http://gitbucket.exmple.com")
|
implicit val context = JsonFormat.Context("http://gitbucket.exmple.com", None)
|
||||||
|
|
||||||
val apiUser = ApiUser(
|
val apiUser = ApiUser(
|
||||||
login = "octocat",
|
login = "octocat",
|
||||||
@@ -281,7 +281,8 @@ class JsonFormatSpec extends FunSuite {
|
|||||||
merged_by = Some(apiUser),
|
merged_by = Some(apiUser),
|
||||||
title = "new-feature",
|
title = "new-feature",
|
||||||
body = "Please pull these awesome changes",
|
body = "Please pull these awesome changes",
|
||||||
user = apiUser
|
user = apiUser,
|
||||||
|
assignee = Some(apiUser)
|
||||||
)
|
)
|
||||||
|
|
||||||
val apiPullRequestJson = s"""{
|
val apiPullRequestJson = s"""{
|
||||||
@@ -311,6 +312,7 @@ class JsonFormatSpec extends FunSuite {
|
|||||||
"title": "new-feature",
|
"title": "new-feature",
|
||||||
"body": "Please pull these awesome changes",
|
"body": "Please pull these awesome changes",
|
||||||
"user": $apiUserJson,
|
"user": $apiUserJson,
|
||||||
|
"assignee": $apiUserJson,
|
||||||
"html_url": "${context.baseUrl}/octocat/Hello-World/pull/1347",
|
"html_url": "${context.baseUrl}/octocat/Hello-World/pull/1347",
|
||||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347",
|
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347",
|
||||||
"commits_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347/commits",
|
"commits_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347/commits",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import org.scalatest.FunSpec
|
|||||||
|
|
||||||
class GitCommandFactorySpec extends FunSpec {
|
class GitCommandFactorySpec extends FunSpec {
|
||||||
|
|
||||||
val factory = new GitCommandFactory("http://localhost:8080")
|
val factory = new GitCommandFactory("http://localhost:8080", None)
|
||||||
|
|
||||||
describe("createCommand") {
|
describe("createCommand") {
|
||||||
it("should return GitReceivePack when command is git-receive-pack"){
|
it("should return GitReceivePack when command is git-receive-pack"){
|
||||||
|
|||||||
@@ -119,8 +119,7 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
|
|||||||
smtp = None,
|
smtp = None,
|
||||||
ldapAuthentication = false,
|
ldapAuthentication = false,
|
||||||
ldap = None,
|
ldap = None,
|
||||||
skinName = "skin-blue",
|
skinName = "skin-blue"
|
||||||
debug = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user