mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 12:15:35 +02:00
Compare commits
4 Commits
3.11
...
maintenanc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7afc3e8f9 | ||
|
|
8e8468b5b1 | ||
|
|
8665a6ee44 | ||
|
|
f4932ab88e |
59
README.md
59
README.md
@@ -9,21 +9,28 @@ The current version of GitBucket provides a basic features below:
|
||||
|
||||
- Public / Private Git repository (http and ssh access)
|
||||
- Repository viewer and online file editing
|
||||
- Repository search (Code and Issues)
|
||||
- Wiki
|
||||
- Issues / Pull request
|
||||
- Issues
|
||||
- Fork / Pull request
|
||||
- Email notification
|
||||
- Activity timeline
|
||||
- Simple user and group management with LDAP integration
|
||||
- Gravatar support
|
||||
- Plug-in system
|
||||
|
||||
If you want to try the development version of GitBucket, see [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/how_to_run.md).
|
||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
||||
|
||||
Installation
|
||||
--------
|
||||
GitBucket requires **Java8**. You have to install beforehand when it's not installed.
|
||||
|
||||
1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases).
|
||||
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
|
||||
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser and logged-in with **root** / **root**.
|
||||
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser.
|
||||
|
||||
If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nginx)
|
||||
|
||||
The default administrator account is **root** and password is **root**.
|
||||
|
||||
or you can start GitBucket by `java -jar gitbucket.war` without servlet container. In this case, GitBucket URL is **http://[hostname]:8080/**. You can specify following options.
|
||||
|
||||
@@ -34,7 +41,35 @@ or you can start GitBucket by `java -jar gitbucket.war` without servlet containe
|
||||
|
||||
To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
||||
|
||||
About installation on Mac or Windows Server (with IIS), configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
||||
For Installation on Windows Server with IIS see [this wiki page](https://github.com/gitbucket/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
|
||||
|
||||
### Mac OS X
|
||||
#### Installing Via Homebrew
|
||||
|
||||
```
|
||||
$ brew install gitbucket
|
||||
==> Downloading https://github.com/takezoe/gitbucket/releases/download/1.10/gitbucket.war
|
||||
######################################################################## 100.0%
|
||||
==> Caveats
|
||||
Note: When using launchctl the port will be 8080.
|
||||
|
||||
To have launchd start gitbucket at login:
|
||||
ln -sfv /usr/local/opt/gitbucket/*.plist ~/Library/LaunchAgents
|
||||
Then to load gitbucket now:
|
||||
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.gitbucket.plist
|
||||
Or, if you don't want/need launchctl, you can just run:
|
||||
java -jar /usr/local/opt/gitbucket/libexec/gitbucket.war
|
||||
==> Summary
|
||||
/usr/local/Cellar/gitbucket/1.10: 3 files, 42M, built in 11 seconds
|
||||
```
|
||||
|
||||
#### Manual Installation
|
||||
On OS X, generate `gitbucket.plist` by [this script](https://raw.githubusercontent.com/gitbucket/gitbucket/master/contrib/macosx/makePlist) and copy it to `~/Library/LaunchAgents/`
|
||||
|
||||
Run the following commands in `Terminal` to
|
||||
|
||||
- start gitbucket: `launchctl load ~/Library/LaunchAgents/gitbucket.plist`
|
||||
- stop gitbucket: `launchctl unload ~/Library/LaunchAgents/gitbucket.plist`
|
||||
|
||||
Plug-ins
|
||||
--------
|
||||
@@ -60,18 +95,10 @@ Support
|
||||
|
||||
Release Notes
|
||||
--------
|
||||
### 3.11 - 30 Jan 2016
|
||||
- Upgrade Scalatra to 2.4
|
||||
- Sidebar and Footer for Wiki
|
||||
- Branch protection and receive hook extension point for plug-in
|
||||
- Limit recent updated repositories list
|
||||
- Issue actions look-alike GitHub
|
||||
- Web API for labels
|
||||
|
||||
### 3.10 - 30 Dec 2015
|
||||
- Move to Bootstrap3
|
||||
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`)
|
||||
- Update xsbt-web-plugin
|
||||
- Update xsbt-web-pligin
|
||||
- Update H2 database
|
||||
|
||||
### 3.9 - 5 Dec 2015
|
||||
@@ -315,3 +342,7 @@ Release Notes
|
||||
|
||||
### 1.0 - 04 Jul 2013
|
||||
- This is a first public release
|
||||
|
||||
Sponsors
|
||||
--------
|
||||
[](https://www.jetbrains.com/idea/)
|
||||
|
||||
159
build.sbt
159
build.sbt
@@ -1,159 +0,0 @@
|
||||
val Organization = "gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "3.11.0"
|
||||
val ScalatraVersion = "2.4.0"
|
||||
val JettyVersion = "9.3.6.v20151106"
|
||||
|
||||
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
||||
|
||||
sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.11.6"
|
||||
|
||||
// dependency settings
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
|
||||
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||
)
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.3.0",
|
||||
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
|
||||
"commons-io" % "commons-io" % "2.4",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.6",
|
||||
"org.apache.commons" % "commons-compress" % "1.10",
|
||||
"org.apache.commons" % "commons-email" % "1.4",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
||||
"org.apache.tika" % "tika-core" % "1.11",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.190",
|
||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
||||
"com.typesafe" % "config" % "1.3.0",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
||||
"org.specs2" %% "specs2-junit" % "3.6.6" % "test"
|
||||
)
|
||||
|
||||
// Twirl settings
|
||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
|
||||
|
||||
// Compiler settings
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps")
|
||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7")
|
||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console")
|
||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
||||
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() )
|
||||
fork in Test := true
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
|
||||
// Assembly settings
|
||||
test in assembly := {}
|
||||
assemblyMergeStrategy in assembly := {
|
||||
case PathList("META-INF", xs @ _*) =>
|
||||
(xs map {_.toLowerCase}) match {
|
||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||
case _ => MergeStrategy.discard
|
||||
}
|
||||
case x => MergeStrategy.first
|
||||
}
|
||||
|
||||
// JRebel
|
||||
jrebel.webLinks += (target in webappPrepare).value
|
||||
jrebel.enabled := System.getenv().get("JREBEL") != null
|
||||
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
||||
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||
}
|
||||
jrebelSettings
|
||||
|
||||
// Create executable war file
|
||||
val executableConfig = config("executable").hide
|
||||
Keys.ivyConfigurations += executableConfig
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
|
||||
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
|
||||
)
|
||||
|
||||
val executableKey = TaskKey[File]("executable")
|
||||
executableKey := {
|
||||
import org.apache.ivy.util.ChecksumHelper
|
||||
import java.util.jar.{ Manifest => JarManifest }
|
||||
import java.util.jar.Attributes.{ Name => AttrName }
|
||||
|
||||
val workDir = Keys.target.value / "executable"
|
||||
val warName = Keys.name.value + ".war"
|
||||
|
||||
val log = streams.value.log
|
||||
log info s"building executable webapp in ${workDir}"
|
||||
|
||||
// initialize temp directory
|
||||
val temp = workDir / "webapp"
|
||||
IO delete temp
|
||||
|
||||
// include jetty classes
|
||||
val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name)
|
||||
jettyJars foreach { jar =>
|
||||
IO unzip (jar, temp, (name:String) =>
|
||||
(name startsWith "javax/") ||
|
||||
(name startsWith "org/")
|
||||
)
|
||||
}
|
||||
|
||||
// include original war file
|
||||
val warFile = (Keys.`package`).value
|
||||
IO unzip (warFile, temp)
|
||||
|
||||
// include launcher classes
|
||||
val classDir = (Keys.classDirectory in Compile).value
|
||||
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */)
|
||||
launchClasses foreach { name =>
|
||||
IO copyFile (classDir / name, temp / name)
|
||||
}
|
||||
|
||||
// zip it up
|
||||
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
|
||||
val manifest = new JarManifest
|
||||
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||
val outputFile = workDir / warName
|
||||
IO jar (contentMappings, outputFile, manifest)
|
||||
|
||||
// generate checksums
|
||||
Seq("md5", "sha1") foreach { algorithm =>
|
||||
IO.write(
|
||||
workDir / (warName + "." + algorithm),
|
||||
ChecksumHelper computeAsString (outputFile, algorithm)
|
||||
)
|
||||
}
|
||||
|
||||
// done
|
||||
log info s"built executable webapp ${outputFile}"
|
||||
outputFile
|
||||
}
|
||||
/*
|
||||
Keys.artifact in (Compile, executableKey) ~= {
|
||||
_ copy (`type` = "war", extension = "war"))
|
||||
}
|
||||
addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
|
||||
*/
|
||||
@@ -8,7 +8,7 @@ To release a new version of GitBucket, add the version definition to the [gitbuc
|
||||
object AutoUpdate {
|
||||
...
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
Version(1, 0)
|
||||
@@ -20,7 +20,7 @@ Next, add a SQL file which updates database schema into [/src/main/resources/upd
|
||||
|
||||
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
|
||||
|
||||
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
||||
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
||||
|
||||
```scala
|
||||
val versions = Seq(
|
||||
|
||||
@@ -8,10 +8,10 @@ This directory has following structure:
|
||||
* /HOME/gitbucket
|
||||
* /repositories
|
||||
* /USER_NAME
|
||||
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||
* /REPO_NAME
|
||||
* / REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||
* / REPO_NAME
|
||||
* /issues (files which are attached to issue)
|
||||
* /REPO_NAME.wiki.git (wiki repository)
|
||||
* / REPO_NAME.wiki.git (wiki repository)
|
||||
* /data
|
||||
* /USER_NAME
|
||||
* /files
|
||||
|
||||
@@ -27,8 +27,7 @@ $ sbt package
|
||||
|
||||
To build executable war file, run
|
||||
|
||||
```
|
||||
$ sbt executable
|
||||
```
|
||||
* Windows: Not available
|
||||
* Linux: `./release/make-release-war.sh`
|
||||
|
||||
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact.
|
||||
at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact.
|
||||
|
||||
148
doc/jrebel.md
148
doc/jrebel.md
@@ -1,148 +0,0 @@
|
||||
JRebel integration (optional)
|
||||
=============================
|
||||
|
||||
[JRebel](http://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:
|
||||
|
||||
```
|
||||
> 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
|
||||
|
||||
Sign up for a [usage plan](https://my.jrebel.com/). You will need to create an account.
|
||||
|
||||
## 2. Download JRebel
|
||||
|
||||
Download the most recent ["nosetup" JRebel zip](http://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
||||
Next, unzip the downloaded file.
|
||||
|
||||
## 3. Activate
|
||||
|
||||
Follow the [instructions on the JRebel website](http://zeroturnaround.com/software/jrebel/download/prev-releases/) 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.
|
||||
|
||||
## 4. Tell jvm where JRebel is
|
||||
|
||||
Fortunately, the gitbucket project is already set up to use JRebel.
|
||||
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:
|
||||
|
||||
```bash
|
||||
export JREBEL=/path/to/jrebel/jrebel.jar
|
||||
```
|
||||
|
||||
For example, if you unzipped your JRebel download in your home directory, you whould use:
|
||||
|
||||
```bash
|
||||
export JREBEL=~/jrebel/jrebel.jar
|
||||
```
|
||||
|
||||
Now reload your shell:
|
||||
|
||||
```
|
||||
$ source ~/.bash_profile # on Mac
|
||||
$ source ~/.bashrc # on Linux
|
||||
```
|
||||
|
||||
## 5. See it in action!
|
||||
|
||||
Now you're ready to use JRebel with the gitbucket.
|
||||
When you run sbt as normal, you will see a long message from JRebel, indicating it has loaded.
|
||||
Here's an abbreviated version of what you will see:
|
||||
|
||||
```
|
||||
$ ./sbt
|
||||
[info] Loading project definition from /git/gitbucket/project
|
||||
[info] Set current project to gitbucket (in build file:/git/gitbucket/)
|
||||
>
|
||||
```
|
||||
|
||||
You will start the servlet container slightly differently now that you're using sbt.
|
||||
|
||||
```
|
||||
> jetty:start
|
||||
:
|
||||
[info] starting server ...
|
||||
[success] Total time: 3 s, completed Jan 3, 2016 9:47:55 PM
|
||||
2016-01-03 21:47:57 JRebel:
|
||||
2016-01-03 21:47:57 JRebel: A newer version '6.3.1' is available for download
|
||||
2016-01-03 21:47:57 JRebel: from http://zeroturnaround.com/software/jrebel/download/
|
||||
2016-01-03 21:47:57 JRebel:
|
||||
2016-01-03 21:47:58 JRebel: Contacting myJRebel server ..
|
||||
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/classes' will be monitored for changes.
|
||||
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/test-classes' will be monitored for changes.
|
||||
2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/webapp' will be monitored for changes.
|
||||
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: JRebel Legacy Agent 6.2.5 (201509291538)
|
||||
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
|
||||
[success] Total time: 0 s, completed Jan 3, 2016 9:13:54 PM
|
||||
1. Waiting for source changes... (press enter to interrupt)
|
||||
```
|
||||
|
||||
Finally, change your code.
|
||||
For example, you can change the title on `src/main/twirl/gitbucket/core/main.scala.html` like this:
|
||||
|
||||
```html
|
||||
:
|
||||
<a class="navbar-brand" href="@path/">
|
||||
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket
|
||||
@defining(AutoUpdate.getCurrentVersion){ version =>
|
||||
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
||||
}
|
||||
change code !!!!!!!!!!!!!!!!
|
||||
</a>
|
||||
:
|
||||
```
|
||||
|
||||
If JRebel is doing is correctly installed you will see a notice for you:
|
||||
|
||||
```
|
||||
1. Waiting for source changes... (press enter to interrupt)
|
||||
2016-01-03 21:48:42 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
||||
[info] Wrote rebel.xml to /git/gitbucket/target/scala-2.11/resource_managed/main/rebel.xml
|
||||
[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:
|
||||
|
||||
```
|
||||
[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM
|
||||
2. Waiting for source changes... (press enter to interrupt)
|
||||
2016-01-03 21:49:13 JRebel: Reloading class 'gitbucket.core.html.main$'.
|
||||
```
|
||||
|
||||
## 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`.
|
||||
@@ -9,4 +9,3 @@ Developer's Guide
|
||||
* [Notification Email](notification.md)
|
||||
* [Automatic Schema Updating](auto_update.md)
|
||||
* [Release Operation](release.md)
|
||||
* [JRebel integration (optional)](jrebel.md)
|
||||
|
||||
@@ -23,7 +23,7 @@ object MyBuild extends Build {
|
||||
object AutoUpdate {
|
||||
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(3, 3), // <---- add this line!!
|
||||
@@ -37,10 +37,11 @@ Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https
|
||||
|
||||
### Make release war file
|
||||
|
||||
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||
Run `release/make-release-war.sh`. The release war file is generated into `target/scala-2.11/gitbucket.war`.
|
||||
|
||||
```bash
|
||||
$sbt executable
|
||||
$ cd release
|
||||
$ ./make-release-war.sh
|
||||
```
|
||||
|
||||
### Deploy assembly jar file
|
||||
|
||||
BIN
embed-jetty/javax.servlet-3.0.0.v201112011016.jar
Normal file
BIN
embed-jetty/javax.servlet-3.0.0.v201112011016.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-continuation-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-continuation-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-http-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-http-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-io-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-io-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-security-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-security-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-server-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-server-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-servlet-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-servlet-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-util-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-util-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-webapp-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-webapp-8.1.16.v20140903.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-xml-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-xml-8.1.16.v20140903.jar
Normal file
Binary file not shown.
11
embed-jetty/update.sh
Executable file
11
embed-jetty/update.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
version=$1
|
||||
output_dir=`dirname $0`
|
||||
git rm -f ${output_dir}/jetty-*.jar
|
||||
for name in 'io' 'servlet' 'xml' 'continuation' 'security' 'util' 'http' 'server' 'webapp'
|
||||
do
|
||||
jar_filename="jetty-${name}-${version}.jar"
|
||||
wget "http://repo1.maven.org/maven2/org/eclipse/jetty/jetty-${name}/${version}/${jar_filename}" -O ${output_dir}/${jar_filename}
|
||||
done
|
||||
git add ${output_dir}/*.jar
|
||||
git commit
|
||||
80
project/build.scala
Normal file
80
project/build.scala
Normal file
@@ -0,0 +1,80 @@
|
||||
import com.earldouglas.xwp.JettyPlugin
|
||||
import play.twirl.sbt.SbtTwirl
|
||||
import sbt.Keys._
|
||||
import sbt._
|
||||
import sbtassembly.AssemblyKeys._
|
||||
import sbtassembly._
|
||||
import JettyPlugin.autoImport._
|
||||
|
||||
object MyBuild extends Build {
|
||||
val Organization = "gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val Version = "3.10.1"
|
||||
val ScalaVersion = "2.11.6"
|
||||
val ScalatraVersion = "2.3.1"
|
||||
|
||||
lazy val project = Project (
|
||||
"gitbucket",
|
||||
file(".")
|
||||
)
|
||||
// .settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
||||
.settings(
|
||||
test in assembly := {},
|
||||
assemblyMergeStrategy in assembly := {
|
||||
case PathList("META-INF", xs @ _*) =>
|
||||
(xs map {_.toLowerCase}) match {
|
||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||
case _ => MergeStrategy.discard
|
||||
}
|
||||
case x => MergeStrategy.first
|
||||
}
|
||||
)
|
||||
.settings(
|
||||
sourcesInBase := false,
|
||||
organization := Organization,
|
||||
name := Name,
|
||||
version := Version,
|
||||
scalaVersion := ScalaVersion,
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
|
||||
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||
),
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.2.201412180340-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.2.201412180340-r",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.2.11",
|
||||
"jp.sf.amateras" %% "scalatra-forms" % "0.2.0",
|
||||
"commons-io" % "commons-io" % "2.4",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.6-SNAPSHOT",
|
||||
"org.apache.commons" % "commons-compress" % "1.9",
|
||||
"org.apache.commons" % "commons-email" % "1.3.3",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.3.6",
|
||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
||||
"org.apache.tika" % "tika-core" % "1.10",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.180",
|
||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided",
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
||||
"com.typesafe" % "config" % "1.2.1",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.3.10",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x" exclude("c3p0","c3p0")
|
||||
),
|
||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml",
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test",
|
||||
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() ),
|
||||
fork in Test := true,
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
).enablePlugins(SbtTwirl, JettyPlugin)
|
||||
}
|
||||
@@ -2,5 +2,4 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")
|
||||
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
|
||||
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")
|
||||
67
release/build.xml
Normal file
67
release/build.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<project name="gitbucket" default="all" basedir="..">
|
||||
|
||||
<property environment="env"/>
|
||||
<property name="target.dir" value="target"/>
|
||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
||||
<property name="jetty.dir" value="embed-jetty"/>
|
||||
<property name="scala.version" value="2.11"/>
|
||||
<property name="gitbucket.version" value="${env.GITBUCKET_VERSION}"/>
|
||||
<property name="jetty.version" value="8.1.16.v20140903"/>
|
||||
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
||||
|
||||
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
|
||||
<os family="windows" />
|
||||
</condition>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="${embed.classes.dir}"/>
|
||||
<delete file="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
||||
</target>
|
||||
|
||||
<target name="war" depends="clean">
|
||||
<exec executable="${sbt.exec}" resolveexecutable="true" failonerror="true">
|
||||
<arg line="clean compile test package" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="embed" depends="war">
|
||||
<mkdir dir="${embed.classes.dir}"/>
|
||||
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/javax.servlet-${servlet.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-continuation-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-http-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-io-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-security-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-server-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-servlet-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-util-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-webapp-${jetty.version}.jar" />
|
||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-xml-${jetty.version}.jar" />
|
||||
|
||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||
basedir="${embed.classes.dir}"
|
||||
update = "true"
|
||||
includes="javax/**,org/**"/>
|
||||
|
||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||
basedir="${target.dir}/scala-${scala.version}/classes"
|
||||
update = "true"
|
||||
includes="JettyLauncher.class,HttpsSupportConnector.class"/>
|
||||
</target>
|
||||
|
||||
<target name="rename" depends="embed">
|
||||
<move file="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
||||
</target>
|
||||
|
||||
<target name="checksum" depends="rename">
|
||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="MD5" format="MD5SUM" forceOverwrite="yes" fileext=".md5"/>
|
||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="SHA" format="MD5SUM" forceOverwrite="yes" fileext=".sha1"/>
|
||||
</target>
|
||||
|
||||
<target name="all" depends="checksum">
|
||||
</target>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -5,15 +5,6 @@ cd ../
|
||||
./sbt.sh clean assembly
|
||||
|
||||
cd release
|
||||
|
||||
if [[ "$GITBUCKET_VERSION" =~ -SNAPSHOT$ ]]; then
|
||||
MVN_DEPLOY_PATH=mvn-snapshot
|
||||
else
|
||||
MVN_DEPLOY_PATH=mvn
|
||||
fi
|
||||
|
||||
echo $MVN_DEPLOY_PATH
|
||||
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=gitbucket\
|
||||
-DartifactId=gitbucket-assembly\
|
||||
@@ -21,4 +12,4 @@ mvn deploy:deploy-file \
|
||||
-Dpackaging=jar\
|
||||
-Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\
|
||||
-DrepositoryId=sourceforge.jp\
|
||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/$MVN_DEPLOY_PATH/
|
||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
||||
|
||||
15
release/make-release-war.sh
Executable file
15
release/make-release-war.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
D="$(dirname "$0")"
|
||||
D="$(cd "${D}"; pwd)"
|
||||
DD="$(dirname "${D}")"
|
||||
(
|
||||
for f in "${D}/env.sh" "${D}/build.xml"; do
|
||||
if [ ! -s "${f}" ]; then
|
||||
echo >&2 "$0: Unable to access file '${f}'"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
. "${D}/env.sh"
|
||||
cd "${DD}"
|
||||
ant -f "${D}/build.xml" all
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
import java.io.File;
|
||||
@@ -29,16 +30,16 @@ public class JettyLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
Server server = new Server(port);
|
||||
Server server = new Server();
|
||||
|
||||
// SelectChannelConnector connector = new SelectChannelConnector();
|
||||
// if(host != null) {
|
||||
// connector.setHost(host);
|
||||
// }
|
||||
// connector.setMaxIdleTime(1000 * 60 * 60);
|
||||
// connector.setSoLingerTime(-1);
|
||||
// connector.setPort(port);
|
||||
// server.addConnector(connector);
|
||||
SelectChannelConnector connector = new SelectChannelConnector();
|
||||
if(host != null) {
|
||||
connector.setHost(host);
|
||||
}
|
||||
connector.setMaxIdleTime(1000 * 60 * 60);
|
||||
connector.setSoLingerTime(-1);
|
||||
connector.setPort(port);
|
||||
server.addConnector(connector);
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
DROP TABLE IF EXISTS PROTECTED_BRANCH;
|
||||
|
||||
CREATE TABLE PROTECTED_BRANCH(
|
||||
USER_NAME VARCHAR(100) NOT NULL,
|
||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||
BRANCH VARCHAR(100) NOT NULL,
|
||||
STATUS_CHECK_ADMIN BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
ALTER TABLE PROTECTED_BRANCH ADD CONSTRAINT IDX_PROTECTED_BRANCH_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, BRANCH);
|
||||
ALTER TABLE PROTECTED_BRANCH ADD CONSTRAINT IDX_PROTECTED_BRANCH_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS PROTECTED_BRANCH_REQUIRE_CONTEXT;
|
||||
CREATE TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT(
|
||||
USER_NAME VARCHAR(100) NOT NULL,
|
||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||
BRANCH VARCHAR(100) NOT NULL,
|
||||
CONTEXT VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT ADD CONSTRAINT IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT);
|
||||
ALTER TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT ADD CONSTRAINT IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, BRANCH) REFERENCES PROTECTED_BRANCH (USER_NAME, REPOSITORY_NAME, BRANCH)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,16 +0,0 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/#get-branch
|
||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||
*/
|
||||
case class ApiBranch(
|
||||
name: String,
|
||||
// commit: ApiBranchCommit,
|
||||
protection: ApiBranchProtection)(repositoryName:RepositoryName) extends FieldSerializable {
|
||||
def _links = Map(
|
||||
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}"))
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.service.ProtectedBranchService
|
||||
import org.json4s._
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]){
|
||||
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||
}
|
||||
|
||||
object ApiBranchProtection{
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtection)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
||||
enabled = info.enabled,
|
||||
required_status_checks = Some(Status(EnforcementLevel(info.enabled, info.includeAdministrators), info.contexts)))
|
||||
val statusNone = Status(Off, Seq.empty)
|
||||
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
||||
sealed class EnforcementLevel(val name: String)
|
||||
case object Off extends EnforcementLevel("off")
|
||||
case object NonAdmins extends EnforcementLevel("non_admins")
|
||||
case object Everyone extends EnforcementLevel("everyone")
|
||||
object EnforcementLevel {
|
||||
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel = if(enabled){
|
||||
if(includeAdministrators){
|
||||
Everyone
|
||||
}else{
|
||||
NonAdmins
|
||||
}
|
||||
}else{
|
||||
Off
|
||||
}
|
||||
}
|
||||
|
||||
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](format => (
|
||||
{
|
||||
case JString("off") => Off
|
||||
case JString("non_admins") => NonAdmins
|
||||
case JString("everyone") => Everyone
|
||||
},
|
||||
{
|
||||
case x: EnforcementLevel => JString(x.name)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.Label
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/labels/
|
||||
*/
|
||||
case class ApiLabel(
|
||||
name: String,
|
||||
color: String)(repositoryName: RepositoryName){
|
||||
var url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/labels/${name}")
|
||||
}
|
||||
|
||||
object ApiLabel{
|
||||
def apply(label:Label, repositoryName: RepositoryName): ApiLabel =
|
||||
ApiLabel(
|
||||
name = label.labelName,
|
||||
color = label.color
|
||||
)(repositoryName)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||
* api form
|
||||
*/
|
||||
case class CreateALabel(
|
||||
name: String,
|
||||
color: String
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
name.length<=100 &&
|
||||
!name.startsWith("_") &&
|
||||
!name.startsWith("-") &&
|
||||
color.length==6 &&
|
||||
color.matches("[a-fA-F0-9+_.]+")
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,7 @@ object JsonFormat {
|
||||
FieldSerializer[ApiCombinedCommitStatus]() +
|
||||
FieldSerializer[ApiPullRequest.Commit]() +
|
||||
FieldSerializer[ApiIssue]() +
|
||||
FieldSerializer[ApiComment]() +
|
||||
FieldSerializer[ApiLabel]() +
|
||||
ApiBranchProtection.enforcementLevelSerializer
|
||||
FieldSerializer[ApiComment]()
|
||||
|
||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||
(
|
||||
|
||||
@@ -12,7 +12,7 @@ import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util._
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
|
||||
@@ -8,7 +8,7 @@ import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.json4s._
|
||||
import org.scalatra._
|
||||
@@ -28,11 +28,7 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
||||
with SystemSettingsService {
|
||||
|
||||
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
|
||||
before("/api/v3/*") {
|
||||
contentType = formats("json")
|
||||
}
|
||||
implicit val jsonFormats = DefaultFormats
|
||||
|
||||
// TODO Scala 2.11
|
||||
// // Don't set content type via Accept header.
|
||||
|
||||
@@ -8,7 +8,7 @@ import gitbucket.core.service.{RepositoryService, ActivityService, AccountServic
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
|
||||
class IndexController extends IndexControllerBase
|
||||
|
||||
@@ -11,7 +11,7 @@ import gitbucket.core.util._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.Markdown
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.Ok
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.api.{ApiError, CreateALabel, ApiLabel, JsonFormat}
|
||||
import gitbucket.core.issues.labels.html
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||
import gitbucket.core.util.{LockUtil, RepositoryName, ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.{NoContent, UnprocessableEntity, Created, Ok}
|
||||
import org.scalatra.Ok
|
||||
|
||||
class LabelsController extends LabelsControllerBase
|
||||
with LabelsService with IssuesService with RepositoryService with AccountService
|
||||
@@ -20,7 +19,7 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
case class LabelForm(labelName: String, color: String)
|
||||
|
||||
val labelForm = mapping(
|
||||
"labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
|
||||
"labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
||||
"labelColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
|
||||
@@ -32,26 +31,6 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
/**
|
||||
* List all labels for this repository
|
||||
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
|
||||
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
|
||||
ApiLabel(label, RepositoryName(repository))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Get a single label
|
||||
* https://developer.github.com/v3/issues/labels/#get-a-single-label
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
|
||||
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||
JsonFormat(ApiLabel(label, RepositoryName(repository)))
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
||||
html.edit(None, repository)
|
||||
})
|
||||
@@ -66,31 +45,6 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
/**
|
||||
* Create a label
|
||||
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
|
||||
(for{
|
||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
|
||||
getLabel(repository.owner, repository.name, labelId).map { label =>
|
||||
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
|
||||
} getOrElse NotFound()
|
||||
} else {
|
||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||
UnprocessableEntity(ApiError(
|
||||
"Validation Failed",
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||
))
|
||||
}
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||
html.edit(Some(label), repository)
|
||||
@@ -107,50 +61,11 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
/**
|
||||
* Update a label
|
||||
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||
(for{
|
||||
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||
JsonFormat(ApiLabel(
|
||||
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||
RepositoryName(repository)))
|
||||
} else {
|
||||
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||
UnprocessableEntity(ApiError(
|
||||
"Validation Failed",
|
||||
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
|
||||
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
||||
Ok()
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete a label
|
||||
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||
deleteLabel(repository.owner, repository.name, label.labelId)
|
||||
NoContent()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Constraint for the identifier such as user name, repository name or page name.
|
||||
*/
|
||||
@@ -165,12 +80,4 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def uniqueLabelName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
|
||||
val owner = params("owner")
|
||||
val repository = params("repository")
|
||||
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import gitbucket.core.issues.milestones.html
|
||||
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class MilestonesController extends MilestonesControllerBase
|
||||
with MilestonesService with RepositoryService with AccountService
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.model.{Account, CommitStatus, CommitState, Repository, PullRequest, Issue, WebHook}
|
||||
import gitbucket.core.model.{Account, CommitState, Repository, PullRequest, Issue}
|
||||
import gitbucket.core.pulls.html
|
||||
import gitbucket.core.service.CommitStatusService
|
||||
import gitbucket.core.service.MergeService
|
||||
@@ -16,7 +16,7 @@ import gitbucket.core.util._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.PersonIdent
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -27,13 +27,13 @@ import scala.collection.JavaConverters._
|
||||
class PullRequestsController extends PullRequestsControllerBase
|
||||
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
||||
with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with CommitStatusService with MergeService with ProtectedBranchService
|
||||
with CommitStatusService with MergeService
|
||||
|
||||
|
||||
trait PullRequestsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
||||
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with CommitStatusService with MergeService with ProtectedBranchService =>
|
||||
with CommitStatusService with MergeService =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
||||
|
||||
@@ -119,8 +119,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
commits,
|
||||
diffs,
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository,
|
||||
flash.toMap.map(f => f._1 -> f._2.toString))
|
||||
repository)
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound
|
||||
@@ -167,34 +166,22 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
|
||||
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
|
||||
params("id").toIntOpt.flatMap{ issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||
val hasConflict = LockUtil.lock(s"${owner}/${name}"){
|
||||
val statuses = getCommitStatues(owner, name, pullreq.commitIdTo)
|
||||
val hasConfrict = LockUtil.lock(s"${owner}/${name}"){
|
||||
checkConflict(owner, name, pullreq.branch, issueId)
|
||||
}
|
||||
val hasMergePermission = hasWritePermission(owner, name, context.loginAccount)
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
|
||||
val mergeStatus = PullRequestService.MergeStatus(
|
||||
hasConflict = hasConflict,
|
||||
commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo),
|
||||
branchProtection = branchProtection,
|
||||
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
|
||||
needStatusCheck = context.loginAccount.map{ u =>
|
||||
branchProtection.needStatusCheck(u.userName)
|
||||
}.getOrElse(true),
|
||||
hasUpdatePermission = hasWritePermission(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) &&
|
||||
context.loginAccount.map{ u =>
|
||||
!getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch).needStatusCheck(u.userName)
|
||||
}.getOrElse(false),
|
||||
hasMergePermission = hasMergePermission,
|
||||
commitIdTo = pullreq.commitIdTo)
|
||||
val hasProblem = hasConfrict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
||||
html.mergeguide(
|
||||
mergeStatus,
|
||||
hasConfrict,
|
||||
hasProblem,
|
||||
issue,
|
||||
pullreq,
|
||||
statuses,
|
||||
repository,
|
||||
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get)
|
||||
}
|
||||
@@ -216,75 +203,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/update_branch")(referrersOnly { baseRepository =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
loginAccount <- context.loginAccount
|
||||
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
owner = pullreq.requestUserName
|
||||
name = pullreq.requestRepositoryName
|
||||
if hasWritePermission(owner, name, context.loginAccount)
|
||||
} yield {
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||
if(branchProtection.needStatusCheck(loginAccount.userName)){
|
||||
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
|
||||
} else {
|
||||
val repository = getRepository(owner, name, context.baseUrl).get
|
||||
LockUtil.lock(s"${owner}/${name}"){
|
||||
val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){
|
||||
pullreq.branch
|
||||
}else{
|
||||
s"${pullreq.userName}:${pullreq.branch}"
|
||||
}
|
||||
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet
|
||||
pullRemote(owner, name, pullreq.requestBranch, pullreq.userName, pullreq.repositoryName, pullreq.branch, loginAccount,
|
||||
"Merge branch '${alias}' into ${pullreq.requestBranch}") match {
|
||||
case None => // conflict
|
||||
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
|
||||
case Some(oldId) =>
|
||||
// update pull request
|
||||
updatePullRequests(owner, name, pullreq.requestBranch)
|
||||
|
||||
using(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
|
||||
// after update branch
|
||||
|
||||
val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}")
|
||||
val commits = git.log.addRange(oldId, newCommitId).call.iterator.asScala.map(c => new JGitUtil.CommitInfo(c)).toList
|
||||
|
||||
commits.foreach{ commit =>
|
||||
if(!existIds.contains(commit.id)){
|
||||
createIssueComment(owner, name, commit)
|
||||
}
|
||||
}
|
||||
|
||||
// record activity
|
||||
recordPushActivity(owner, name, loginAccount.userName, pullreq.branch, commits)
|
||||
|
||||
// close issue by commit message
|
||||
if(pullreq.requestBranch == repository.repository.defaultBranch){
|
||||
commits.map{ commit =>
|
||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||
}
|
||||
}
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHookByRequestBranch("synchronize", repository, pullreq.requestBranch, baseUrl, loginAccount)
|
||||
callWebHookOf(owner, name, WebHook.Push) {
|
||||
for {
|
||||
ownerAccount <- getAccountByUserName(owner)
|
||||
} yield {
|
||||
WebHookService.WebHookPushPayload(git, loginAccount, pullreq.requestBranch, repository, commits, ownerAccount, oldId = oldId, newId = newCommitId)
|
||||
}
|
||||
}
|
||||
}
|
||||
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
|
||||
}
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
}
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
|
||||
params("id").toIntOpt.flatMap { issueId =>
|
||||
val owner = repository.owner
|
||||
@@ -610,15 +528,4 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
}
|
||||
|
||||
// TODO: same as gitbucket.core.servlet.CommitLogHook ...
|
||||
private def createIssueComment(owner: String, repository: String, commit: CommitInfo) = {
|
||||
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
|
||||
if(getIssue(owner, repository, issueId).isDefined){
|
||||
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
|
||||
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.settings.html
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, WebHookService, ProtectedBranchService, CommitStatusService}
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, WebHookService}
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -18,29 +18,23 @@ import org.eclipse.jgit.lib.ObjectId
|
||||
|
||||
|
||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||
with RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService
|
||||
with RepositoryService with AccountService with WebHookService
|
||||
with OwnerAuthenticator with UsersAuthenticator
|
||||
|
||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService
|
||||
self: RepositoryService with AccountService with WebHookService
|
||||
with OwnerAuthenticator with UsersAuthenticator =>
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(repositoryName: String, description: Option[String], isPrivate: Boolean)
|
||||
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"repositoryName" -> trim(label("Repository Name", text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||
)(OptionsForm.apply)
|
||||
|
||||
// for default branch
|
||||
case class DefaultBranchForm(defaultBranch: String)
|
||||
|
||||
val defaultBranchForm = mapping(
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100))))
|
||||
)(DefaultBranchForm.apply)
|
||||
|
||||
// for collaborator addition
|
||||
case class CollaboratorForm(userName: String)
|
||||
|
||||
@@ -81,10 +75,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Save the repository options.
|
||||
*/
|
||||
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
||||
val defaultBranch = if(repository.branchList.isEmpty) "master" else form.defaultBranch
|
||||
saveRepositoryOptions(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.description,
|
||||
defaultBranch,
|
||||
repository.repository.parentUserName.map { _ =>
|
||||
repository.repository.isPrivate
|
||||
} getOrElse form.isPrivate
|
||||
@@ -102,61 +98,14 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
}
|
||||
// Change repository HEAD
|
||||
using(Git.open(getRepositoryDir(repository.owner, form.repositoryName))) { git =>
|
||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + defaultBranch)
|
||||
}
|
||||
flash += "info" -> "Repository settings has been updated."
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||
})
|
||||
|
||||
/** branch settings */
|
||||
get("/:owner/:repository/settings/branches")(ownerOnly { repository =>
|
||||
val protecteions = getProtectedBranchList(repository.owner, repository.name)
|
||||
html.branches(repository, protecteions, flash.get("info"))
|
||||
});
|
||||
|
||||
/** Update default branch */
|
||||
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
|
||||
if(repository.branchList.find(_ == form.defaultBranch).isEmpty){
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
} else {
|
||||
saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch)
|
||||
// Change repository HEAD
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + form.defaultBranch)
|
||||
}
|
||||
flash += "info" -> "Repository default branch has been updated."
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
}
|
||||
})
|
||||
|
||||
/** Branch protection for branch */
|
||||
get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
val branch = params("branch")
|
||||
if(repository.branchList.find(_ == branch).isEmpty){
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
} else {
|
||||
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val lastWeeks = getRecentStatuesContexts(repository.owner, repository.name, org.joda.time.LocalDateTime.now.minusWeeks(1).toDate).toSet
|
||||
val knownContexts = (lastWeeks ++ protection.status.contexts).toSeq.sortBy(identity)
|
||||
html.branchprotection(repository, branch, protection, knownContexts, flash.get("info"))
|
||||
}
|
||||
})
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
(for{
|
||||
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
} yield {
|
||||
if(protection.enabled){
|
||||
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, protection)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Display the Collaborators page.
|
||||
*/
|
||||
|
||||
@@ -14,11 +14,12 @@ import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.{Account, CommitState, WebHook}
|
||||
import gitbucket.core.service.CommitStatusService
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
@@ -33,7 +34,7 @@ import org.scalatra._
|
||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
@@ -41,7 +42,7 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService =>
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService =>
|
||||
|
||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||
@@ -110,7 +111,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||
enableTaskList = params("enableTaskList").toBoolean,
|
||||
enableAnchor = false,
|
||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
@@ -221,15 +221,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
||||
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")),
|
||||
protectedBranch)
|
||||
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
|
||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
@@ -237,8 +234,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
val paths = path.split("/")
|
||||
html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
|
||||
JGitUtil.getContentInfo(git, path, objectId),
|
||||
protectedBranch)
|
||||
JGitUtil.getContentInfo(git, path, objectId))
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
@@ -307,7 +303,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
getPathObjectId(git, path, revCommit).flatMap { objectId =>
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||
contentType = FileUtil.getMimeType(path)
|
||||
contentType = "application/octet-stream"
|
||||
response.setContentLength(loader.getSize.toInt)
|
||||
loader.copyTo(response.outputStream)
|
||||
()
|
||||
@@ -328,7 +324,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
if(raw){
|
||||
// Download (This route is left for backword compatibility)
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||
contentType = FileUtil.getMimeType(path)
|
||||
contentType = "application/octet-stream"
|
||||
response.setContentLength(loader.getSize.toInt)
|
||||
loader.copyTo(response.outputStream)
|
||||
()
|
||||
@@ -489,7 +485,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* Displays branches.
|
||||
*/
|
||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet
|
||||
val branches = JGitUtil.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
@@ -497,7 +492,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.map(br => (br, getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId), protectedBranches.contains(br.name)))
|
||||
.map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId))
|
||||
.reverse
|
||||
|
||||
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
@@ -634,7 +629,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val parentPath = if (path == ".") Nil else path.split("/").toList
|
||||
// process README.md or README.markdown
|
||||
val readme = files.find { file =>
|
||||
!file.isDirectory && readmeFiles.contains(file.name.toLowerCase)
|
||||
readmeFiles.contains(file.name.toLowerCase)
|
||||
}.map { file =>
|
||||
val path = (file.name :: parentPath.reverse).reverse
|
||||
path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
|
||||
@@ -688,7 +683,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
headName, loginAccount.fullName, loginAccount.mailAddress, message)
|
||||
|
||||
inserter.flush()
|
||||
inserter.close()
|
||||
inserter.release()
|
||||
|
||||
// update refs
|
||||
val refUpdate = git.getRepository.updateRef(headName)
|
||||
|
||||
@@ -5,7 +5,7 @@ import gitbucket.core.service._
|
||||
import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits}
|
||||
import ControlUtil._
|
||||
import Implicits._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class SearchController extends SearchControllerBase
|
||||
with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator
|
||||
|
||||
@@ -5,7 +5,7 @@ import gitbucket.core.service.{AccountService, SystemSettingsService}
|
||||
import gitbucket.core.util.AdminAuthenticator
|
||||
import gitbucket.core.ssh.SshServer
|
||||
import SystemSettingsService._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class SystemSettingsController extends SystemSettingsControllerBase
|
||||
with AccountService with AdminAuthenticator
|
||||
|
||||
@@ -7,7 +7,7 @@ import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
@@ -38,9 +38,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||
})
|
||||
|
||||
@@ -49,9 +47,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||
})
|
||||
|
||||
@@ -128,11 +124,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
||||
}
|
||||
if(notReservedPageName(form.pageName)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -148,11 +140,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||
|
||||
if(notReservedPageName(form.pageName)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -198,15 +186,13 @@ trait WikiControllerBase extends ControllerBase {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(value.exists("\\/:*?\"<>|".contains(_))){
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))){
|
||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
||||
Some(s"${name} starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def notReservedPageName(value: String) = ! (Array[String]("_Sidebar","_Footer") contains value)
|
||||
|
||||
private def conflictForNew: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
targetWikiPage.map { _ =>
|
||||
|
||||
@@ -26,16 +26,12 @@ protected[model] trait TemplateComponent { self: Profile =>
|
||||
|
||||
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val labelId = column[Int]("LABEL_ID")
|
||||
val labelName = column[String]("LABEL_NAME")
|
||||
|
||||
def byLabel(owner: String, repository: String, labelId: Int) =
|
||||
byRepository(owner, repository) && (this.labelId === labelId.bind)
|
||||
|
||||
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.labelId === labelId)
|
||||
|
||||
def byLabel(owner: String, repository: String, labelName: String) =
|
||||
byRepository(userName, repositoryName) && (this.labelName === labelName.bind)
|
||||
}
|
||||
|
||||
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||
@@ -58,9 +54,4 @@ protected[model] trait TemplateComponent { self: Profile =>
|
||||
byRepository(userName, repositoryName) && (this.commitId === commitId)
|
||||
}
|
||||
|
||||
trait BranchTemplate extends BasicTemplate{ self: Table[_] =>
|
||||
val branch = column[String]("BRANCH")
|
||||
def byBranch(owner: String, repository: String, branchName: String) = byRepository(owner, repository) && (branch === branchName.bind)
|
||||
def byBranch(owner: Column[String], repository: Column[String], branchName: Column[String]) = byRepository(owner, repository) && (this.branch === branchName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
||||
val creator = column[String]("CREATOR")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply)
|
||||
def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> (CommitStatus.tupled, CommitStatus.unapply)
|
||||
def byPrimaryKey(id: Int) = commitStatusId === id.bind
|
||||
}
|
||||
}
|
||||
@@ -38,20 +38,7 @@ case class CommitStatus(
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date
|
||||
)
|
||||
object CommitStatus {
|
||||
def pending(owner: String, repository: String, context: String) = CommitStatus(
|
||||
commitStatusId = 0,
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
commitId = "",
|
||||
context = context,
|
||||
state = CommitState.PENDING,
|
||||
targetUrl = None,
|
||||
description = Some("Waiting for status to be reported"),
|
||||
creator = "",
|
||||
registeredDate = new java.util.Date(),
|
||||
updatedDate = new java.util.Date())
|
||||
}
|
||||
|
||||
|
||||
sealed abstract class CommitState(val name: String)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
|
||||
|
||||
class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate {
|
||||
override val labelId = column[Int]("LABEL_ID", O AutoInc)
|
||||
override val labelName = column[String]("LABEL_NAME")
|
||||
val labelName = column[String]("LABEL_NAME")
|
||||
val color = column[String]("COLOR")
|
||||
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)
|
||||
|
||||
|
||||
@@ -50,6 +50,5 @@ trait CoreProfile extends ProfileProvider with Profile
|
||||
with WebHookComponent
|
||||
with WebHookEventComponent
|
||||
with PluginComponent
|
||||
with ProtectedBranchComponent
|
||||
|
||||
object Profile extends CoreProfile
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
import scala.slick.lifted.MappedTo
|
||||
import scala.slick.jdbc._
|
||||
|
||||
trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
|
||||
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
|
||||
def * = (userName, repositoryName, branch, statusCheckAdmin) <> (ProtectedBranch.tupled, ProtectedBranch.unapply)
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String) = byBranch(userName, repositoryName, branch)
|
||||
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], branch: Column[String]) = byBranch(userName, repositoryName, branch)
|
||||
}
|
||||
|
||||
lazy val ProtectedBranchContexts = TableQuery[ProtectedBranchContexts]
|
||||
class ProtectedBranchContexts(tag: Tag) extends Table[ProtectedBranchContext](tag, "PROTECTED_BRANCH_REQUIRE_CONTEXT") with BranchTemplate {
|
||||
val context = column[String]("CONTEXT")
|
||||
def * = (userName, repositoryName, branch, context) <> (ProtectedBranchContext.tupled, ProtectedBranchContext.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case class ProtectedBranch(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
statusCheckAdmin: Boolean)
|
||||
|
||||
|
||||
case class ProtectedBranchContext(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
context: String)
|
||||
@@ -67,16 +67,6 @@ trait Plugin {
|
||||
*/
|
||||
def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil
|
||||
|
||||
/**
|
||||
* Override to add receive hooks.
|
||||
*/
|
||||
val receiveHooks: Seq[ReceiveHook] = Nil
|
||||
|
||||
/**
|
||||
* Override to add receive hooks.
|
||||
*/
|
||||
def receiveHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[ReceiveHook] = Nil
|
||||
|
||||
/**
|
||||
* This method is invoked in initialization of plugin system.
|
||||
* Register plugin functionality to PluginRegistry.
|
||||
@@ -97,9 +87,6 @@ trait Plugin {
|
||||
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>
|
||||
registry.addRepositoryRouting(routing)
|
||||
}
|
||||
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook =>
|
||||
registry.addReceiveHook(receiveHook)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@ import javax.servlet.ServletContext
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import gitbucket.core.controller.{Context, ControllerBase}
|
||||
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
@@ -30,8 +29,6 @@ class PluginRegistry {
|
||||
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer
|
||||
)
|
||||
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting]
|
||||
private val receiveHooks = new ListBuffer[ReceiveHook]
|
||||
receiveHooks += new ProtectedBranchReceiveHook()
|
||||
|
||||
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
||||
plugins += pluginInfo
|
||||
@@ -101,12 +98,6 @@ class PluginRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
def addReceiveHook(commitHook: ReceiveHook): Unit = {
|
||||
receiveHooks += commitHook
|
||||
}
|
||||
|
||||
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
||||
|
||||
private case class GlobalAction(
|
||||
method: String,
|
||||
path: String,
|
||||
@@ -178,7 +169,7 @@ object PluginRegistry {
|
||||
))
|
||||
|
||||
} catch {
|
||||
case e: Throwable => {
|
||||
case e: Exception => {
|
||||
logger.error(s"Error during plugin initialization", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import gitbucket.core.model.Profile._
|
||||
import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand}
|
||||
import profile.simple._
|
||||
|
||||
trait ReceiveHook {
|
||||
|
||||
def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||
(implicit session: Session): Option[String] = None
|
||||
|
||||
def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||
(implicit session: Session): Unit = ()
|
||||
|
||||
}
|
||||
@@ -7,51 +7,46 @@ import gitbucket.core.model.{CommitState, CommitStatus, Account}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import org.joda.time.LocalDateTime
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
|
||||
|
||||
trait CommitStatusService {
|
||||
/** insert or update */
|
||||
def createCommitStatus(userName: String, repositoryName: String, sha: String, context: String, state: CommitState,
|
||||
targetUrl: Option[String], description: Option[String], now: java.util.Date, creator: Account)(implicit s: Session): Int =
|
||||
CommitStatuses
|
||||
.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind )
|
||||
def createCommitStatus(userName: String, repositoryName: String, sha:String, context:String, state:CommitState, targetUrl:Option[String], description:Option[String], now:java.util.Date, creator:Account)(implicit s: Session): Int =
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind )
|
||||
.map(_.commitStatusId).firstOption match {
|
||||
case Some(id: Int) => {
|
||||
CommitStatuses.filter(_.byPrimaryKey(id)).map { t =>
|
||||
(t.state , t.targetUrl , t.updatedDate , t.creator, t.description)
|
||||
}.update((state, targetUrl, now, creator.userName, description))
|
||||
id
|
||||
}
|
||||
case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus(
|
||||
userName = userName,
|
||||
repositoryName = repositoryName,
|
||||
commitId = sha,
|
||||
context = context,
|
||||
state = state,
|
||||
targetUrl = targetUrl,
|
||||
description = description,
|
||||
creator = creator.userName,
|
||||
registeredDate = now,
|
||||
updatedDate = now)
|
||||
case Some(id:Int) => {
|
||||
CommitStatuses.filter(_.byPrimaryKey(id)).map{
|
||||
t => (t.state , t.targetUrl , t.updatedDate , t.creator, t.description)
|
||||
}.update( (state, targetUrl, now, creator.userName, description) )
|
||||
id
|
||||
}
|
||||
case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus(
|
||||
userName = userName,
|
||||
repositoryName = repositoryName,
|
||||
commitId = sha,
|
||||
context = context,
|
||||
state = state,
|
||||
targetUrl = targetUrl,
|
||||
description = description,
|
||||
creator = creator.userName,
|
||||
registeredDate = now,
|
||||
updatedDate = now)
|
||||
}
|
||||
|
||||
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] =
|
||||
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption
|
||||
|
||||
def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)(implicit s: Session) :Option[CommitStatus] =
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind).firstOption
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind ).firstOption
|
||||
|
||||
def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[CommitStatus] =
|
||||
byCommitStatues(userName, repositoryName, sha).list
|
||||
|
||||
def getRecentStatuesContexts(userName: String, repositoryName: String, time: java.util.Date)(implicit s: Session) :List[String] =
|
||||
CommitStatuses.filter(t => t.byRepository(userName, repositoryName)).filter(t => t.updatedDate > time.bind).groupBy(_.context).map(_._1).list
|
||||
|
||||
def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[(CommitStatus, Account)] =
|
||||
byCommitStatues(userName, repositoryName, sha).innerJoin(Accounts).filter { case (t, a) => t.creator === a.userName }.list
|
||||
byCommitStatues(userName, repositoryName, sha).innerJoin(Accounts)
|
||||
.filter{ case (t,a) => t.creator === a.userName }.list
|
||||
|
||||
protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha)).sortBy(_.updatedDate desc)
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) ).sortBy(_.updatedDate desc)
|
||||
|
||||
}
|
||||
@@ -12,9 +12,6 @@ trait LabelsService {
|
||||
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
|
||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
||||
|
||||
def getLabel(owner: String, repository: String, labelName: String)(implicit s: Session): Option[Label] =
|
||||
Labels.filter(_.byLabel(owner, repository, labelName)).firstOption
|
||||
|
||||
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int =
|
||||
Labels returning Labels.map(_.labelId) += Label(
|
||||
userName = owner,
|
||||
|
||||
@@ -10,9 +10,10 @@ import org.eclipse.jgit.merge.MergeStrategy
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
||||
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent, Repository}
|
||||
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
|
||||
|
||||
trait MergeService {
|
||||
import MergeService._
|
||||
/**
|
||||
@@ -51,30 +52,26 @@ trait MergeService {
|
||||
/**
|
||||
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||
*/
|
||||
def tryMergeRemote(localUserName: String, localRepositoryName: String, localBranch: String,
|
||||
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String): Option[(ObjectId, ObjectId, ObjectId)] = {
|
||||
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
val remoteRefName = s"refs/heads/${remoteBranch}"
|
||||
val tmpRefName = s"refs/remote-temp/${remoteUserName}/${remoteRepositoryName}/${remoteBranch}"
|
||||
def checkConflict(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
|
||||
using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git =>
|
||||
val remoteRefName = s"refs/heads/${branch}"
|
||||
val tmpRefName = s"refs/merge-check/${userName}/${branch}"
|
||||
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
|
||||
try {
|
||||
// fetch objects from origin repository branch
|
||||
git.fetch
|
||||
.setRemote(getRepositoryDir(remoteUserName, remoteRepositoryName).toURI.toString)
|
||||
.setRemote(getRepositoryDir(userName, repositoryName).toURI.toString)
|
||||
.setRefSpecs(refSpec)
|
||||
.call
|
||||
// merge conflict check
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
||||
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${localBranch}")
|
||||
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}")
|
||||
val mergeTip = git.getRepository.resolve(tmpRefName)
|
||||
try {
|
||||
if(merger.merge(mergeBaseTip, mergeTip)){
|
||||
Some((merger.getResultTreeId, mergeBaseTip, mergeTip))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
!merger.merge(mergeBaseTip, mergeTip)
|
||||
} catch {
|
||||
case e: NoMergeBaseException => None
|
||||
case e: NoMergeBaseException => true
|
||||
}
|
||||
} finally {
|
||||
val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
|
||||
@@ -83,54 +80,8 @@ trait MergeService {
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||
*/
|
||||
def checkConflict(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean =
|
||||
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).isEmpty
|
||||
|
||||
def pullRemote(localUserName: String, localRepositoryName: String, localBranch: String,
|
||||
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String,
|
||||
loginAccount: Account, message: String): Option[ObjectId] = {
|
||||
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map{ case (newTreeId, oldBaseId, oldHeadId) =>
|
||||
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
val newCommit = Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId))
|
||||
Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge"))
|
||||
}
|
||||
oldBaseId
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
object MergeService{
|
||||
object Util{
|
||||
// return treeId
|
||||
def createMergeCommit(repository: Repository, treeId: ObjectId, committer: PersonIdent, message: String, parents: Seq[ObjectId]): ObjectId = {
|
||||
val mergeCommit = new CommitBuilder()
|
||||
mergeCommit.setTreeId(treeId)
|
||||
mergeCommit.setParentIds(parents:_*)
|
||||
mergeCommit.setAuthor(committer)
|
||||
mergeCommit.setCommitter(committer)
|
||||
mergeCommit.setMessage(message)
|
||||
// insertObject and got mergeCommit Object Id
|
||||
val inserter = repository.newObjectInserter
|
||||
val mergeCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
inserter.close()
|
||||
mergeCommitId
|
||||
}
|
||||
def updateRefs(repository: Repository, ref: String, newObjectId: ObjectId, force: Boolean, committer: PersonIdent, refLogMessage: Option[String] = None):Unit = {
|
||||
// update refs
|
||||
val refUpdate = repository.updateRef(ref)
|
||||
refUpdate.setNewObjectId(newObjectId)
|
||||
refUpdate.setForceUpdate(force)
|
||||
refUpdate.setRefLogIdent(committer)
|
||||
refLogMessage.map(refUpdate.setRefLogMessage(_, true))
|
||||
refUpdate.update()
|
||||
}
|
||||
}
|
||||
case class MergeCacheInfo(git:Git, branch:String, issueId:Int){
|
||||
val repository = git.getRepository
|
||||
val mergedBranchName = s"refs/pull/${issueId}/merge"
|
||||
@@ -139,17 +90,17 @@ object MergeService{
|
||||
lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head")
|
||||
def checkConflictCache(): Option[Boolean] = {
|
||||
Option(repository.resolve(mergedBranchName)).flatMap{ merged =>
|
||||
if(parseCommit(merged).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||
if(parseCommit( merged ).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||
// merged branch exists
|
||||
Some(false)
|
||||
} else {
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}.orElse(Option(repository.resolve(conflictedBranchName)).flatMap{ conflicted =>
|
||||
if(parseCommit(conflicted).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||
if(parseCommit( conflicted ).getParents().toSet == Set( mergeBaseTip, mergeTip )){
|
||||
// conflict branch exists
|
||||
Some(true)
|
||||
} else {
|
||||
}else{
|
||||
None
|
||||
}
|
||||
})
|
||||
@@ -169,12 +120,17 @@ object MergeService{
|
||||
def updateBranch(treeId:ObjectId, message:String, branchName:String){
|
||||
// creates merge commit
|
||||
val mergeCommitId = createMergeCommit(treeId, committer, message)
|
||||
Util.updateRefs(repository, branchName, mergeCommitId, true, committer)
|
||||
// update refs
|
||||
val refUpdate = repository.updateRef(branchName)
|
||||
refUpdate.setNewObjectId(mergeCommitId)
|
||||
refUpdate.setForceUpdate(true)
|
||||
refUpdate.setRefLogIdent(committer)
|
||||
refUpdate.update()
|
||||
}
|
||||
if(!conflicted){
|
||||
updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName)
|
||||
git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call()
|
||||
} else {
|
||||
}else{
|
||||
updateBranch(mergeTipCommit.getTree().getId(), s"can't merge ${mergeTip.name} into ${mergeBaseTip.name}", conflictedBranchName)
|
||||
git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call()
|
||||
}
|
||||
@@ -189,12 +145,28 @@ object MergeService{
|
||||
// creates merge commit
|
||||
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
|
||||
// update refs
|
||||
Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
|
||||
val refUpdate = repository.updateRef(s"refs/heads/${branch}")
|
||||
refUpdate.setNewObjectId(mergeCommitId)
|
||||
refUpdate.setForceUpdate(false)
|
||||
refUpdate.setRefLogIdent(committer)
|
||||
refUpdate.setRefLogMessage("merged", true)
|
||||
refUpdate.update()
|
||||
}
|
||||
// return treeId
|
||||
private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) =
|
||||
Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip))
|
||||
|
||||
private def createMergeCommit(treeId:ObjectId, committer:PersonIdent, message:String) = {
|
||||
val mergeCommit = new CommitBuilder()
|
||||
mergeCommit.setTreeId(treeId)
|
||||
mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*)
|
||||
mergeCommit.setAuthor(committer)
|
||||
mergeCommit.setCommitter(committer)
|
||||
mergeCommit.setMessage(message)
|
||||
// insertObject and got mergeCommit Object Id
|
||||
val inserter = repository.newObjectInserter
|
||||
val mergeCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
inserter.release()
|
||||
mergeCommitId
|
||||
}
|
||||
private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id))
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.plugin.ReceiveHook
|
||||
import profile.simple._
|
||||
|
||||
import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand}
|
||||
|
||||
|
||||
trait ProtectedBranchService {
|
||||
import ProtectedBranchService._
|
||||
private def getProtectedBranchInfoOpt(owner: String, repository: String, branch: String)(implicit session: Session): Option[ProtectedBranchInfo] =
|
||||
ProtectedBranches
|
||||
.leftJoin(ProtectedBranchContexts)
|
||||
.on{ case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) }
|
||||
.map{ case (pb, c) => pb -> c.context.? }
|
||||
.filter(_._1.byPrimaryKey(owner, repository, branch))
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.map(p => p._1 -> p._2.flatMap(_._2))
|
||||
.map{ case (t1, contexts) =>
|
||||
new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin)
|
||||
}.headOption
|
||||
|
||||
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit session: Session): ProtectedBranchInfo =
|
||||
getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(ProtectedBranchInfo.disabled(owner, repository))
|
||||
|
||||
def getProtectedBranchList(owner: String, repository: String)(implicit session: Session): List[String] =
|
||||
ProtectedBranches.filter(_.byRepository(owner, repository)).map(_.branch).list
|
||||
|
||||
def enableBranchProtection(owner: String, repository: String, branch:String, includeAdministrators: Boolean, contexts: Seq[String])
|
||||
(implicit session: Session): Unit = {
|
||||
disableBranchProtection(owner, repository, branch)
|
||||
ProtectedBranches.insert(new ProtectedBranch(owner, repository, branch, includeAdministrators && contexts.nonEmpty))
|
||||
contexts.map{ context =>
|
||||
ProtectedBranchContexts.insert(new ProtectedBranchContext(owner, repository, branch, context))
|
||||
}
|
||||
}
|
||||
|
||||
def disableBranchProtection(owner: String, repository: String, branch:String)(implicit session: Session): Unit =
|
||||
ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete
|
||||
|
||||
}
|
||||
|
||||
object ProtectedBranchService {
|
||||
|
||||
class ProtectedBranchReceiveHook extends ReceiveHook with ProtectedBranchService {
|
||||
override def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)
|
||||
(implicit session: Session): Option[String] = {
|
||||
val branch = command.getRefName.stripPrefix("refs/heads/")
|
||||
if(branch != command.getRefName){
|
||||
getProtectedBranchInfo(owner, repository, branch).getStopReason(receivePack.isAllowNonFastForwards, command, pusher)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case class ProtectedBranchInfo(
|
||||
owner: String,
|
||||
repository: String,
|
||||
enabled: Boolean,
|
||||
/**
|
||||
* Require status checks to pass before merging
|
||||
* Choose which status checks must pass before branches can be merged into test.
|
||||
* When enabled, commits must first be pushed to another branch,
|
||||
* then merged or pushed directly to test after status checks have passed.
|
||||
*/
|
||||
contexts: Seq[String],
|
||||
/**
|
||||
* Include administrators
|
||||
* Enforce required status checks for repository administrators.
|
||||
*/
|
||||
includeAdministrators: Boolean) extends AccountService with CommitStatusService {
|
||||
|
||||
def isAdministrator(pusher: String)(implicit session: Session): Boolean =
|
||||
pusher == owner || getGroupMembers(owner).filter(gm => gm.userName == pusher && gm.isManager).nonEmpty
|
||||
|
||||
/**
|
||||
* Can't be force pushed
|
||||
* Can't be deleted
|
||||
* Can't have changes merged into them until required status checks pass
|
||||
*/
|
||||
def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)(implicit session: Session): Option[String] = {
|
||||
if(enabled){
|
||||
command.getType() match {
|
||||
case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
|
||||
Some("Cannot force-push to a protected branch")
|
||||
case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||
unSuccessedContexts(command.getNewId.name) match {
|
||||
case s if s.size == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.size >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
}
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
Some("Cannot delete a protected branch")
|
||||
case _ => None
|
||||
}
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}
|
||||
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = if(contexts.isEmpty){
|
||||
Set.empty
|
||||
} else {
|
||||
contexts.toSet -- getCommitStatues(owner, repository, sha1).filter(_.state == CommitState.SUCCESS).map(_.context).toSet
|
||||
}
|
||||
def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match {
|
||||
case _ if !enabled => false
|
||||
case _ if contexts.isEmpty => false
|
||||
case _ if includeAdministrators => true
|
||||
case p if isAdministrator(p) => false
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
object ProtectedBranchInfo{
|
||||
def disabled(owner: String, repository: String): ProtectedBranchInfo = ProtectedBranchInfo(owner, repository, false, Nil, false)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{Account, Issue, PullRequest, WebHook, CommitStatus, CommitState}
|
||||
import gitbucket.core.model.{Account, Issue, PullRequest, WebHook}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import profile.simple._
|
||||
@@ -145,29 +145,4 @@ object PullRequestService {
|
||||
|
||||
case class PullRequestCount(userName: String, count: Int)
|
||||
|
||||
case class MergeStatus(
|
||||
hasConflict: Boolean,
|
||||
commitStatues:List[CommitStatus],
|
||||
branchProtection: ProtectedBranchService.ProtectedBranchInfo,
|
||||
branchIsOutOfDate: Boolean,
|
||||
hasUpdatePermission: Boolean,
|
||||
needStatusCheck: Boolean,
|
||||
hasMergePermission: Boolean,
|
||||
commitIdTo: String){
|
||||
|
||||
val statuses: List[CommitStatus] =
|
||||
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
|
||||
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS))
|
||||
val hasProblem = hasRequiredStatusProblem || hasConflict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
lazy val commitStateSummary:(CommitState, String) = {
|
||||
val stateMap = statuses.groupBy(_.state)
|
||||
val state = CommitState.combine(stateMap.keySet)
|
||||
val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")
|
||||
state -> summary
|
||||
}
|
||||
lazy val statusesAndRequired:List[(CommitStatus, Boolean)] = statuses.map{ s => s -> branchProtection.contexts.exists(_==s.context) }
|
||||
lazy val isAllSuccess = commitStateSummary._1==CommitState.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ trait RepositorySearchService { self: IssuesService =>
|
||||
}
|
||||
}
|
||||
}
|
||||
treeWalk.close()
|
||||
revWalk.close()
|
||||
treeWalk.release
|
||||
revWalk.release
|
||||
|
||||
list.toList
|
||||
}
|
||||
|
||||
@@ -46,20 +46,18 @@ trait RepositoryService { self: AccountService =>
|
||||
(Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository =>
|
||||
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
|
||||
|
||||
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val webHookEvents = WebHookEvents .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val commitComments = CommitComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val commitStatuses = CommitStatuses .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val protectedBranches = ProtectedBranches .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val protectedBranchContexts = ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val webHookEvents = WebHookEvents .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
|
||||
Repositories.filter { t =>
|
||||
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
|
||||
@@ -92,13 +90,11 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
)} :_*)
|
||||
|
||||
PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
CommitComments .insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
CommitStatuses .insertAll(commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
ProtectedBranches .insertAll(protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
ProtectedBranchContexts.insertAll(protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
CommitComments.insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
CommitStatuses.insertAll(commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
|
||||
// Update source repository of pull requests
|
||||
PullRequests.filter { t =>
|
||||
@@ -313,17 +309,11 @@ trait RepositoryService { self: AccountService =>
|
||||
/**
|
||||
* Save repository options.
|
||||
*/
|
||||
def saveRepositoryOptions(userName: String, repositoryName: String,
|
||||
description: Option[String], isPrivate: Boolean)(implicit s: Session): Unit =
|
||||
def saveRepositoryOptions(userName: String, repositoryName: String,
|
||||
description: Option[String], defaultBranch: String, isPrivate: Boolean)(implicit s: Session): Unit =
|
||||
Repositories.filter(_.byRepository(userName, repositoryName))
|
||||
.map { r => (r.description.?, r.isPrivate, r.updatedDate) }
|
||||
.update (description, isPrivate, currentDate)
|
||||
|
||||
def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
|
||||
defaultBranch: String)(implicit s: Session): Unit =
|
||||
Repositories.filter(_.byRepository(userName, repositoryName))
|
||||
.map { r => r.defaultBranch }
|
||||
.update (defaultBranch)
|
||||
.map { r => (r.description.?, r.defaultBranch, r.isPrivate, r.updatedDate) }
|
||||
.update (description, defaultBranch, isPrivate, currentDate)
|
||||
|
||||
/**
|
||||
* Add collaborator to the repository.
|
||||
|
||||
@@ -93,7 +93,7 @@ trait WikiService {
|
||||
def getWikiPageList(owner: String, repository: String): List[String] = {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||
JGitUtil.getFileList(git, "master", ".")
|
||||
.filter(_.name.endsWith(".md")).filterNot(_.name.startsWith("_"))
|
||||
.filter(_.name.endsWith(".md"))
|
||||
.map(_.name.stripSuffix(".md"))
|
||||
.sortBy(x => x)
|
||||
}
|
||||
|
||||
@@ -18,10 +18,9 @@ import gitbucket.core.util.Directory
|
||||
object AutoUpdate {
|
||||
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(3, 11),
|
||||
new Version(3, 10),
|
||||
new Version(3, 9),
|
||||
new Version(3, 8),
|
||||
@@ -156,7 +155,7 @@ object AutoUpdate {
|
||||
)
|
||||
|
||||
/**
|
||||
* The head version of GitBucket.
|
||||
* The head version of BitBucket.
|
||||
*/
|
||||
val headVersion = versions.head
|
||||
|
||||
|
||||
@@ -111,21 +111,13 @@ import scala.collection.JavaConverters._
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session)
|
||||
extends PostReceiveHook with PreReceiveHook
|
||||
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService
|
||||
with WebHookPullRequestService with ProtectedBranchService {
|
||||
with WebHookPullRequestService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
private var existIds: Seq[String] = Nil
|
||||
|
||||
def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||
try {
|
||||
commands.asScala.foreach { command =>
|
||||
// call pre-commit hook
|
||||
PluginRegistry().getReceiveHooks
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher))
|
||||
.headOption.foreach { error =>
|
||||
command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error)
|
||||
}
|
||||
}
|
||||
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
existIds = JGitUtil.getAllCommitIds(git)
|
||||
}
|
||||
@@ -215,9 +207,6 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
newId = command.getNewId(), oldId = command.getOldId())
|
||||
}
|
||||
}
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach(_.postReceive(owner, repository, receivePack, command, pusher))
|
||||
}
|
||||
}
|
||||
// update repository last modified time.
|
||||
|
||||
@@ -23,10 +23,10 @@ object GitCommand {
|
||||
abstract class GitCommand() extends Command {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||
@volatile protected var err: OutputStream = null
|
||||
@volatile protected var in: InputStream = null
|
||||
@volatile protected var out: OutputStream = null
|
||||
@volatile protected var callback: ExitCallback = null
|
||||
protected var err: OutputStream = null
|
||||
protected var in: InputStream = null
|
||||
protected var out: OutputStream = null
|
||||
protected var callback: ExitCallback = null
|
||||
|
||||
protected def runTask(user: String)(implicit session: Session): Unit
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import scala.util.control.Exception._
|
||||
import scala.language.reflectiveCalls
|
||||
|
||||
@@ -29,6 +31,12 @@ object ControlUtil {
|
||||
git2.getRepository.close()
|
||||
}
|
||||
|
||||
def using[T](revWalk: RevWalk)(f: RevWalk => T): T =
|
||||
try f(revWalk) finally revWalk.release()
|
||||
|
||||
def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T =
|
||||
try f(treeWalk) finally treeWalk.release()
|
||||
|
||||
def ignore[T](f: => Unit): Unit = try {
|
||||
f
|
||||
} catch {
|
||||
|
||||
@@ -110,8 +110,6 @@ object JGitUtil {
|
||||
newIsImage: Boolean,
|
||||
oldObjectId: Option[String],
|
||||
newObjectId: Option[String],
|
||||
oldMode: String,
|
||||
newMode: String,
|
||||
tooLarge: Boolean
|
||||
)
|
||||
|
||||
@@ -517,8 +515,6 @@ object JGitUtil {
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = None,
|
||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||
oldMode = treeWalk.getFileMode(0).toString,
|
||||
newMode = treeWalk.getFileMode(0).toString,
|
||||
tooLarge = false
|
||||
)
|
||||
} else {
|
||||
@@ -532,8 +528,6 @@ object JGitUtil {
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = None,
|
||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||
oldMode = treeWalk.getFileMode(0).toString,
|
||||
newMode = treeWalk.getFileMode(0).toString,
|
||||
tooLarge = false
|
||||
)
|
||||
}))
|
||||
@@ -568,8 +562,6 @@ object JGitUtil {
|
||||
newIsImage = false,
|
||||
oldObjectId = Option(diff.getOldId).map(_.name),
|
||||
newObjectId = Option(diff.getNewId).map(_.name),
|
||||
oldMode = diff.getOldMode.toString,
|
||||
newMode = diff.getNewMode.toString,
|
||||
tooLarge = true
|
||||
)
|
||||
} else {
|
||||
@@ -586,8 +578,6 @@ object JGitUtil {
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = Option(diff.getOldId).map(_.name),
|
||||
newObjectId = Option(diff.getNewId).map(_.name),
|
||||
oldMode = diff.getOldMode.toString,
|
||||
newMode = diff.getNewMode.toString,
|
||||
tooLarge = false
|
||||
)
|
||||
} else {
|
||||
@@ -601,8 +591,6 @@ object JGitUtil {
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = Option(diff.getOldId).map(_.name),
|
||||
newObjectId = Option(diff.getNewId).map(_.name),
|
||||
oldMode = diff.getOldMode.toString,
|
||||
newMode = diff.getNewMode.toString,
|
||||
tooLarge = false
|
||||
)
|
||||
}
|
||||
@@ -641,7 +629,7 @@ object JGitUtil {
|
||||
|
||||
def initRepository(dir: java.io.File): Unit =
|
||||
using(new RepositoryBuilder().setGitDir(dir).setBare.build){ repository =>
|
||||
repository.create(true)
|
||||
repository.create
|
||||
setReceivePack(repository)
|
||||
}
|
||||
|
||||
@@ -700,7 +688,7 @@ object JGitUtil {
|
||||
|
||||
val newHeadId = inserter.insert(newCommit)
|
||||
inserter.flush()
|
||||
inserter.close()
|
||||
inserter.release()
|
||||
|
||||
val refUpdate = git.getRepository.updateRef(ref)
|
||||
refUpdate.setNewObjectId(newHeadId)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
trait Validations {
|
||||
|
||||
@@ -33,30 +33,28 @@ object Markdown {
|
||||
enableTaskList: Boolean = false,
|
||||
hasWritePermission: Boolean = false,
|
||||
pages: List[String] = Nil)(implicit context: Context): String = {
|
||||
// escape issue id
|
||||
val s = if(enableRefsLink){
|
||||
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
|
||||
} else markdown
|
||||
|
||||
// escape task list
|
||||
val source = if(enableTaskList) escapeTaskList(markdown) else markdown
|
||||
val source = if(enableTaskList){
|
||||
escapeTaskList(s)
|
||||
} else s
|
||||
|
||||
val options = new Options()
|
||||
options.setSanitize(true)
|
||||
options.setBreaks(enableLineBreaks)
|
||||
|
||||
val renderer = new GitBucketMarkedRenderer(options, repository,
|
||||
enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages)
|
||||
|
||||
val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages)
|
||||
Marked.marked(source, options, renderer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends markedj Renderer for GitBucket
|
||||
*/
|
||||
class GitBucketMarkedRenderer(options: Options,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean,
|
||||
pages: List[String])
|
||||
(implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache {
|
||||
|
||||
@@ -64,14 +62,11 @@ object Markdown {
|
||||
val id = generateAnchorName(text)
|
||||
val out = new StringBuilder()
|
||||
|
||||
out.append("<h" + level + " id=\"" + options.getHeaderPrefix + id + "\"")
|
||||
out.append("<h" + level + " id=\"" + options.getHeaderPrefix + id + "\" class=\"markdown-head\">")
|
||||
|
||||
if(enableAnchor){
|
||||
out.append(" class=\"markdown-head\">")
|
||||
out.append("<a class=\"markdown-anchor-link\" href=\"#" + id + "\"><span class=\"octicon octicon-link\"></span></a>")
|
||||
out.append("<a class=\"markdown-anchor-link\" href=\"#" + id + "\"></a>")
|
||||
out.append("<a class=\"markdown-anchor\" name=\"" + id + "\"></a>")
|
||||
} else {
|
||||
out.append(">")
|
||||
}
|
||||
|
||||
out.append(text)
|
||||
@@ -88,27 +83,28 @@ object Markdown {
|
||||
var listType: String = null
|
||||
if (ordered) {
|
||||
listType = "ol"
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
listType = "ul"
|
||||
}
|
||||
if(body.contains("""class="task-list-item-checkbox"""")){
|
||||
"<" + listType + " class=\"task-list\">\n" + body + "</" + listType + ">\n"
|
||||
return "<" + listType + " class=\"task-list\">\n" + body + "</" + listType + ">\n"
|
||||
} else {
|
||||
"<" + listType + ">\n" + body + "</" + listType + ">\n"
|
||||
return "<" + listType + ">\n" + body + "</" + listType + ">\n"
|
||||
}
|
||||
}
|
||||
|
||||
override def listitem(text: String): String = {
|
||||
if(text.contains("""class="task-list-item-checkbox" """)){
|
||||
"<li class=\"task-list-item\">" + text + "</li>\n"
|
||||
return "<li class=\"task-list-item\">" + text + "</li>\n"
|
||||
} else {
|
||||
"<li>" + text + "</li>\n"
|
||||
return "<li>" + text + "</li>\n"
|
||||
}
|
||||
}
|
||||
|
||||
override def text(text: String): String = {
|
||||
// convert commit id and username to link.
|
||||
val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "#", false) else text
|
||||
val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "issue:", false) else text
|
||||
|
||||
// convert task list to checkbox.
|
||||
val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1
|
||||
|
||||
@@ -89,7 +89,6 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableAnchor: Boolean = true,
|
||||
enableTaskList: Boolean = false,
|
||||
hasWritePermission: Boolean = false,
|
||||
pages: List[String] = Nil)(implicit context: Context): Html =
|
||||
@@ -98,7 +97,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
repository = repository,
|
||||
enableWikiLink = enableWikiLink,
|
||||
enableRefsLink = enableRefsLink,
|
||||
enableAnchor = enableAnchor,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = enableLineBreaks,
|
||||
enableTaskList = enableTaskList,
|
||||
hasWritePermission = hasWritePermission,
|
||||
@@ -289,10 +288,10 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
}
|
||||
|
||||
def commitStateIcon(state: CommitState) = Html(state match {
|
||||
case CommitState.PENDING => """<i style="color:inherit;width:inherit;height:inherit" class="octicon octicon-primitive-dot"></i>"""
|
||||
case CommitState.SUCCESS => """<i style="color:inherit;width:inherit;height:inherit" class="octicon octicon-check"></i>"""
|
||||
case CommitState.ERROR => """<i style="color:inherit;width:inherit;height:inherit" class="octicon octicon-x"></i>"""
|
||||
case CommitState.FAILURE => """<i style="color:inherit;width:inherit;height:inherit" class="octicon octicon-x"></i>"""
|
||||
case CommitState.PENDING => "●"
|
||||
case CommitState.SUCCESS => "✔"
|
||||
case CommitState.ERROR => "×"
|
||||
case CommitState.FAILURE => "×"
|
||||
})
|
||||
|
||||
def commitStateText(state: CommitState, commitId:String) = state match {
|
||||
|
||||
@@ -54,8 +54,9 @@
|
||||
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
</div>
|
||||
}
|
||||
<span class="diffstat"><i class="octicon octicon-diff-renamed"></i></span>
|
||||
<span class="monospace">@diff.oldPath → @diff.newPath</span>
|
||||
<span class="diffstat">
|
||||
<i class="octicon octicon-diff-renamed"></i>
|
||||
</span> @diff.oldPath -> @diff.newPath
|
||||
}
|
||||
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
|
||||
@if(newCommitId.isDefined){
|
||||
@@ -72,7 +73,7 @@
|
||||
<i class="octicon octicon-diff-modified"></i>
|
||||
}
|
||||
</span>
|
||||
<span class="monospace">@diff.newPath</span>
|
||||
@diff.newPath
|
||||
}
|
||||
@if(diff.changeType == ChangeType.DELETE){
|
||||
@if(oldCommitId.isDefined){
|
||||
@@ -81,22 +82,16 @@
|
||||
<a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-default btn-sm" title="View the whole file at version @oldCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
</div>
|
||||
}
|
||||
<span class="diffstat"><i class="octicon octicon-diff-removed"></i></span>
|
||||
<span class="monospace">@diff.oldPath</span>
|
||||
}
|
||||
@if(diff.oldMode != diff.newMode){
|
||||
<span class="monospace">@diff.oldMode → @diff.newMode</span>
|
||||
<span class="diffstat">
|
||||
<i class="octicon octicon-diff-removed"></i>
|
||||
</span> @diff.oldPath
|
||||
}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0;">
|
||||
@if(diff.oldObjectId == diff.newObjectId){
|
||||
@if(diff.oldPath != diff.newPath){
|
||||
<div class="diff-same">File renamed without changes</div>
|
||||
} else {
|
||||
<div class="diff-same">File mode changed</div>
|
||||
}
|
||||
<div class="diff-same">File renamed without changes</div>
|
||||
} else {
|
||||
@if(diff.newContent != None || diff.oldContent != None){
|
||||
<div id="diffText-@i" class="diffText"></div>
|
||||
@@ -104,7 +99,7 @@
|
||||
<textarea id="oldText-@i" style="display: none;" data-file-name="@diff.newPath">@diff.oldContent.getOrElse("")</textarea>
|
||||
} else {
|
||||
@if(diff.newIsImage || diff.oldIsImage){
|
||||
<div class="diff-image-render diff2up">
|
||||
<div class="diff-image-render diff2up">@diff.oldIsImage @diff.newIsImage
|
||||
@if(oldCommitId.isDefined && diff.oldIsImage){
|
||||
<div class="diff-image-frame diff-old"><img src="@url(repository)/raw/@oldCommitId.get/@diff.oldPath" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@(page: Int, count: Int, limit: Int, width: Int, baseURL: String)
|
||||
@defining(gitbucket.core.view.Pagination(page, count, limit, width)){ p =>
|
||||
@if(p.count > p.limit){
|
||||
<div>
|
||||
<ul class="pagination">
|
||||
<div class="pagination">
|
||||
<ul>
|
||||
@if(page == 1){
|
||||
<li class="disabled"><span>◀</span></li>
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
@import gitbucket.core._
|
||||
@import gitbucket.core.view.helpers._
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs fill-width" style="margin-top: 12px; margin-bottom: 10px;">
|
||||
<ul class="nav nav-tabs fill-width pull-left" style="@*margin-top: 12px; *@margin-bottom: 10px;">
|
||||
<li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li>
|
||||
<li><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
}
|
||||
@if(userRepositories.size > max){
|
||||
<li class="list-group-item show-more">
|
||||
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
|
||||
<a href="javascript:void(0);" id="show-more-repos">Show more @{userRepositories.size - max} pages...</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@@ -62,19 +62,12 @@
|
||||
@if(recentRepositories.isEmpty){
|
||||
<li class="list-group-item">No repositories</li>
|
||||
} else {
|
||||
@defining(20){ max =>
|
||||
@recentRepositories.zipWithIndex.map { case (repository, i) =>
|
||||
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
|
||||
@recentRepositories.map { repository =>
|
||||
<li class="list-group-item repo-link">
|
||||
@helper.html.repositoryicon(repository, false)
|
||||
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
||||
</li>
|
||||
}
|
||||
@if(recentRepositories.size > max){
|
||||
<li class="list-group-item show-more">
|
||||
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -84,7 +77,7 @@
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#show-more-repos, #show-more-recent-repos').click(function(e){
|
||||
$('#show-more-repos').click(function(e){
|
||||
$(e.target).parents('ul.list-group').find('li.repo-link').show();
|
||||
$(e.target).parents('li.show-more').remove();
|
||||
});
|
||||
|
||||
@@ -30,12 +30,10 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@issueOrPullRequest()={ @if(issue.isDefined && issue.get.isPullRequest)( "pull request" )else( "issue" ) }
|
||||
|
||||
@comments.map {
|
||||
case comment: gitbucket.core.model.IssueComment => {
|
||||
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"
|
||||
&& comment.action != "commit" && comment.action != "refer"){
|
||||
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
|
||||
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
|
||||
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
|
||||
<div class="panel-heading">
|
||||
@@ -44,7 +42,7 @@
|
||||
@if(comment.action == "comment"){
|
||||
commented
|
||||
} else {
|
||||
referenced the @issueOrPullRequest()
|
||||
@if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
|
||||
}
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</span>
|
||||
@@ -55,114 +53,79 @@
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="panel-body issue-content markdown-body" id="commentContent-@comment.commentId">
|
||||
@markdown(
|
||||
markdown = comment.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if(comment.action == "commit"){
|
||||
@defining({
|
||||
val (content, id) = " ([a-f0-9]{40})$".r.findFirstMatchIn(comment.content)
|
||||
.map(m => (m.before.toString -> Some(m.group(1))))
|
||||
.getOrElse(comment.content -> None)
|
||||
val head = content.take(100).takeWhile(_ != '\n')
|
||||
(id, head, if(head == content){ None }else{ Some(content.drop(head.length).dropWhile(_ == '\n')) }.filter(_.nonEmpty))
|
||||
}){ case (commitId, head, rest) =>
|
||||
<div class="discussion-item discussion-item-commit">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span>
|
||||
@avatar(comment.commentedUserName, 16)
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
added a commit that referenced this @issueOrPullRequest()
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
<div style="discussion-item-content">
|
||||
@commitId.map{ id =>
|
||||
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
||||
@defining(comment.content.substring(comment.content.length - 40)){ id =>
|
||||
<span class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></span>
|
||||
}
|
||||
<span class="discussion-item-content-head"><i class="octicon octicon-git-commit"></i></span>
|
||||
@link(head, repository)
|
||||
@rest.map{ content =>
|
||||
<a href="javascript:void(0)" class="omit" onclick="$(this.parentNode).find('.discussion-item-content-text').toggle()">...</a>
|
||||
<pre class="reset discussion-item-content-text" style="display:none">@link(content, repository)</pre>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if(comment.action == "refer"){
|
||||
<div class="discussion-item discussion-item-refer">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span>
|
||||
@avatar(comment.commentedUserName, 16)
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
referenced the @issueOrPullRequest()
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
<div style="discussion-item-content">
|
||||
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
|
||||
<strong>@issueLink(repository, issueId.toInt): @rest.mkString(":")</strong>
|
||||
<div class="panel-body issue-content markdown-body" id="commentContent-@comment.commentId">
|
||||
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
||||
@markdown(
|
||||
markdown = comment.content.substring(0, comment.content.length - 41),
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
} else {
|
||||
@if(comment.action == "refer"){
|
||||
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
|
||||
<strong>@issueLink(repository, issueId.toInt): @rest.mkString(":")</strong>
|
||||
}
|
||||
} else {
|
||||
@markdown(
|
||||
markdown = comment.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if(comment.action == "merge"){
|
||||
<div class="discussion-item discussion-item-merge">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-git-merge"></i></span>
|
||||
@avatar(comment.commentedUserName, 16)
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
merged commit
|
||||
<code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into
|
||||
@if(pullreq.get.requestUserName == repository.owner){
|
||||
<code>@pullreq.map(_.branch)</code> from <code>@pullreq.map(_.requestBranch)</code>
|
||||
} else {
|
||||
<code>@pullreq.map(_.userName):@pullreq.map(_.branch)</code> from <code>@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</code>
|
||||
}
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
<div class="small" style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<span class="label label-info">Merged</span>
|
||||
@avatar(comment.commentedUserName, 20)
|
||||
@user(comment.commentedUserName, styleClass="username strong") merged commit <code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into
|
||||
@if(pullreq.get.requestUserName == repository.owner){
|
||||
<span class="label label-info monospace">@pullreq.map(_.branch)</span> from <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span>
|
||||
} else {
|
||||
<span class="label label-info monospace">@pullreq.map(_.userName):@pullreq.map(_.branch)</span> from <span class="label label-info monospace">@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</span>
|
||||
}
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
}
|
||||
@if(comment.action == "close" || comment.action == "close_comment"){
|
||||
<div class="discussion-item discussion-item-close">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span>
|
||||
@avatar(comment.commentedUserName, 16)
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
close @issueOrPullRequest()
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
<div class="issue-comment-action">
|
||||
<i class="octicon octicon-circle-slash danger"></i>
|
||||
@avatar(comment.commentedUserName, 20)
|
||||
@if(issue.isDefined && issue.get.isPullRequest){
|
||||
@user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate)
|
||||
} else {
|
||||
@user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if(comment.action == "reopen" || comment.action == "reopen_comment"){
|
||||
<div class="discussion-item discussion-item-reopen">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-primitive-dot"></i></span>
|
||||
@avatar(comment.commentedUserName, 16)
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
reopened the @issueOrPullRequest()
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
<div class="issue-comment-action issue-reopened">
|
||||
<i class="octicon octicon-primitive-dot"></i>
|
||||
@avatar(comment.commentedUserName, 20)
|
||||
@user(comment.commentedUserName, styleClass="username strong") reopened the issue @helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
}
|
||||
@if(comment.action == "delete_branch"){
|
||||
<div class="discussion-item discussion-item-delete_branch">
|
||||
<div class="discussion-item-header">
|
||||
<span class="discussion-item-icon"><i class="octicon octicon-git-branch"></i></span>
|
||||
@avatar(comment.commentedUserName, 16)
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
deleted the <code>@pullreq.map(_.requestBranch)</code> branch
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
<div class="issue-comment-action">
|
||||
<span class="label">Deleted</span>
|
||||
@avatar(comment.commentedUserName, 20)
|
||||
@user(comment.commentedUserName, styleClass="username strong") deleted the <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span> branch @helper.html.datetimeago(comment.registeredDate)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No labels to show.
|
||||
@if(hasWritePermission){
|
||||
Click on the "New Label" button above to create one.
|
||||
<a href="@url(repository)/issues/labels/new">Create a new label.</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -78,7 +78,8 @@
|
||||
<div class="pull-right" style="margin-top: 6px;">
|
||||
<div class="btn-group" style="margin-right: 8px;">
|
||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#">
|
||||
<i class="octicon octicon-plus" style="color: black; font-size: 20px; vertical-align: middle;height:20px !important;"></i><span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||
<i class="octicon octicon-plus" style="color: black; font-size: 20px; vertical-align: middle;height:20px !important;"></i>
|
||||
<span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="@path/new">New repository</a></li>
|
||||
@@ -86,8 +87,8 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#" data-toggle="tooltip" data-placement="bottom" title="Signed is as @loginAccount.get.userName">
|
||||
@avatar(loginAccount.get.userName, 16)<span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#" data-toggle="tooltip" data-placement="bottom" title="Signed is as @loginAccount.get.userName">@avatar(loginAccount.get.userName, 16)
|
||||
<span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i>
|
||||
@if(expand){ @label}
|
||||
@if(expand && count > 0){
|
||||
<div class="pull-right"><span class="badge">@count</span></div>
|
||||
<div class="pull-right"><span class="label">@count</span></div>
|
||||
}
|
||||
</a>
|
||||
</li>
|
||||
@@ -105,7 +105,7 @@
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="pull-left" style="width: @if(expand){770px} else {895px};">
|
||||
<div style="margin-right: @if(expand){180px} else {55px};">
|
||||
@if(expand){
|
||||
@repository.repository.description.map { description =>
|
||||
<p class="description">@detectAndRenderLinks(description)</p>
|
||||
@@ -116,11 +116,7 @@
|
||||
<td style="width: 33%; text-align: center;">
|
||||
<a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link">
|
||||
<i class="octicon octicon-history"></i>
|
||||
@if(repository.commitCount > 10000){
|
||||
<strong>10000+</strong> commits
|
||||
} else {
|
||||
<strong>@repository.commitCount</strong> commits
|
||||
}
|
||||
<strong>@repository.commitCount</strong> commits
|
||||
</a>
|
||||
</td>
|
||||
<td style="width: 33%; text-align: center;">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
case comment: gitbucket.core.model.IssueComment => Some(comment)
|
||||
case other => None
|
||||
}.exists(_.action == "merge")){ merged =>
|
||||
@if(!issue.closed){
|
||||
@if(hasWritePermission && !issue.closed){
|
||||
<div class="check-conflict" style="display: none;">
|
||||
<div class="box issue-comment-box" style="background-color: #fbeed5">
|
||||
<div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;">
|
||||
@@ -40,9 +40,9 @@
|
||||
<span class="small muted">You're all set-the <span class="label label-info monospace">@pullreq.requestBranch</span> branch can be safely deleted.</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
|
||||
}
|
||||
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
@issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
@@ -55,12 +55,10 @@ $(function(){
|
||||
$('#merge-pull-request').show();
|
||||
});
|
||||
|
||||
var checkConflict = $('.check-conflict').show();
|
||||
if(checkConflict.length){
|
||||
$.get('@url(repository)/pull/@issue.issueId/mergeguide', function(data){ $('.check-conflict').html(data); });
|
||||
}
|
||||
|
||||
@if(hasWritePermission){
|
||||
$('.check-conflict').show();
|
||||
$.get('@url(repository)/pull/@issue.issueId/mergeguide', function(data){ $('.check-conflict').html(data); });
|
||||
|
||||
$('.delete-branch').click(function(e){
|
||||
var branchName = $(e.target).data('name');
|
||||
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
|
||||
|
||||
@@ -1,139 +1,120 @@
|
||||
@(status: gitbucket.core.service.PullRequestService.MergeStatus,
|
||||
@(hasConflict: Boolean,
|
||||
hasProblem: Boolean,
|
||||
issue: gitbucket.core.model.Issue,
|
||||
pullreq: gitbucket.core.model.PullRequest,
|
||||
statuses: List[model.CommitStatus],
|
||||
originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.service.SystemSettingsService
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@import model.CommitState
|
||||
<div class="box issue-comment-box" style="background-color: @if(status.hasProblem){ #fbeed5 }else{ #d8f5cd };">
|
||||
<div class="box-content issue-content" style="border: 1px solid @if(status.hasProblem){ #c09853 }else{ #95c97e };padding:0">
|
||||
<div class="box issue-comment-box" style="background-color: @if(hasProblem){ #fbeed5 }else{ #d8f5cd };">
|
||||
<div class="box-content issue-content" style="border: 1px solid @if(hasProblem){ #c09853 }else{ #95c97e }; padding: 10px;">
|
||||
<div id="merge-pull-request">
|
||||
@if(!status.statuses.isEmpty){
|
||||
@if(!statuses.isEmpty){
|
||||
<div class="build-statuses">
|
||||
@defining(status.commitStateSummary){ case (summaryState, summary) =>
|
||||
<div class="build-status-item-header">
|
||||
<a class="pull-right" id="toggle-all-checks"></a>
|
||||
<span class="build-status-icon text-@{summaryState.name}">@commitStateIcon(summaryState)</span>
|
||||
<strong class="text-@{summaryState.name}">@commitStateText(summaryState, pullreq.commitIdTo)</strong>
|
||||
<span class="text-@{summaryState.name}">— @summary checks</span>
|
||||
</div>
|
||||
}
|
||||
<div class="build-statuses-list" style="@if(status.isAllSuccess){ display:none; }else{ }">
|
||||
@status.statusesAndRequired.map{ case (status, required) =>
|
||||
@if(statuses.size==1){
|
||||
@defining(statuses.head){ status =>
|
||||
<div class="build-status-item">
|
||||
<div class="pull-right">
|
||||
@if(required){ <span class="label">Required</span> }
|
||||
@status.targetUrl.map{ url => <a href="@url">Details</a> }
|
||||
</div>
|
||||
@status.targetUrl.map{ url => <a class="pull-right" href="@url">Details</a> }
|
||||
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
|
||||
<strong>@status.context</strong>
|
||||
<strong class="text-@{status.state.name}">@commitStateText(status.state, pullreq.commitIdTo)</strong>
|
||||
@status.description.map{ desc => <span class="muted">— @desc</span> }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div style="padding:15px">
|
||||
@if(status.hasConflict){
|
||||
<div class="merge-indicator merge-indicator-alert"><span class="octicon octicon-alert"></span></div>
|
||||
<span class="strong">This branch has conflicts that must be resolved</span>
|
||||
<div class="small">
|
||||
@if(status.hasMergePermission){
|
||||
<a href="#" class="show-command-line">Use the command line</a> to resolve conflicts before continuing.
|
||||
} else {
|
||||
Only those with write access to this repository can merge pull requests.
|
||||
}
|
||||
</div>
|
||||
} else { @if(status.branchIsOutOfDate){
|
||||
@if(status.hasUpdatePermission){
|
||||
<div class="pull-right">
|
||||
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/update_branch">
|
||||
<input type="hidden" name="expected_head_oid" value="@pullreq.commitIdFrom">
|
||||
<button class="btn"@if(!status.canUpdate){ disabled="true"} id="update-branch-button">Update branch</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
<div class="merge-indicator merge-indicator-alert"><span class="octicon octicon-alert"></span></div>
|
||||
<span class="strong">This branch is out-of-date with the base branch</span>
|
||||
<div class="small">
|
||||
Merge the latest changes from <code>@pullreq.branch</code> into this branch.
|
||||
</div>
|
||||
} else { @if(status.hasRequiredStatusProblem) {
|
||||
<div class="merge-indicator merge-indicator-warning"><span class="octicon octicon-primitive-dot"></span></div>
|
||||
<span class="strong">Required statuses must pass before merging.</span>
|
||||
<div class="small">
|
||||
All required status checks on this pull request must run successfully to enable automatic merging.
|
||||
</div>
|
||||
} else {
|
||||
<div class="merge-indicator merge-indicator-success"><span class="octicon octicon-check"></span></div>
|
||||
@if(status.hasMergePermission){
|
||||
<span class="strong">Merging can be performed automatically.</span>
|
||||
<div class="small">
|
||||
Merging can be performed automatically.
|
||||
</div>
|
||||
} else {
|
||||
<span class="strong">This branch has no conflicts with the base branch.</span>
|
||||
<div class="small">
|
||||
Only those with write access to this repository can merge pull requests.
|
||||
</div>
|
||||
@defining(statuses.groupBy(_.state)){ stateMap =>
|
||||
@defining(CommitState.combine(stateMap.keySet)){ state =>
|
||||
<div class="build-status-item">
|
||||
<a class="pull-right" id="toggle-all-checks"></a>
|
||||
<span class="build-status-icon text-@{state.name}">@commitStateIcon(state)</span>
|
||||
<strong class="text-@{state.name}">@commitStateText(state, pullreq.commitIdTo)</strong>
|
||||
<span class="text-@{state.name}">— @{stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")} checks</span>
|
||||
</div>
|
||||
<div class="build-statuses-list" style="@if(state==CommitState.SUCCESS){ display:none; }else{ }">
|
||||
@statuses.map{ status =>
|
||||
<div class="build-status-item">
|
||||
@status.targetUrl.map{ url => <a class="pull-right" href="@url">Details</a> }
|
||||
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
|
||||
<span class="text-@{status.state.name}">@status.context</span>
|
||||
@status.description.map{ desc => <span class="muted">— @desc</span> }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
} } }
|
||||
</div>
|
||||
@if(status.hasMergePermission){
|
||||
<div style="padding:15px;border-top:solid 1px #e5e5e5;background:#fafafa">
|
||||
<input type="button" class="btn @if(!status.hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/>
|
||||
You can also merge branches on the <a href="#" class="show-command-line">command line</a>.
|
||||
<div id="command-line" style="display: none;margin-top: 15px;">
|
||||
<hr />
|
||||
@if(status.hasConflict){
|
||||
<span class="strong">Checkout via command line</span>
|
||||
<p>
|
||||
If you cannot merge a pull request automatically here, you have the option of checking
|
||||
it out via command line to resolve conflicts and perform a manual merge.
|
||||
</p>
|
||||
} else {
|
||||
<span class="strong">Merging via command line</span>
|
||||
<p>
|
||||
If you do not want to use the merge button or an automatic merge cannot be performed,
|
||||
you can perform a manual merge on the command line.
|
||||
</p>
|
||||
}
|
||||
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl, true){
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button>
|
||||
@if(settings.ssh && loginAccount.isDefined){
|
||||
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button>
|
||||
}
|
||||
</div>
|
||||
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly />
|
||||
}
|
||||
<div>
|
||||
<p>
|
||||
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes.
|
||||
</p>
|
||||
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
|
||||
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
|
||||
@helper.html.copy("merge-command-copy-1", command){
|
||||
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<span class="strong">Step 2:</span> Merge the changes and update on the server.
|
||||
</p>
|
||||
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
|
||||
s"git push origin ${pullreq.branch}"){ command =>
|
||||
@helper.html.copy("merge-command-copy-2", command){
|
||||
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;">@command</pre>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="pull-right">
|
||||
<input type="button" class="btn @if(!hasProblem){ btn-success }else{ btn-default }" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict){ disabled="true"}/>
|
||||
</div>
|
||||
<div>
|
||||
@if(hasConflict){
|
||||
<span class="strong">We can’t automatically merge this pull request.</span>
|
||||
} else {
|
||||
@if(hasProblem){
|
||||
<span class="strong">Merge with caution!</span>
|
||||
} else {
|
||||
<span class="strong">This pull request can be automatically merged.</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="small">
|
||||
@if(hasConflict){
|
||||
<a href="#" id="show-command-line">Use the command line</a> to resolve conflicts before continuing.
|
||||
} else {
|
||||
You can also merge branches on the <a href="#" id="show-command-line">command line</a>.
|
||||
}
|
||||
</div>
|
||||
<div id="command-line" style="display: none;">
|
||||
<hr>
|
||||
@if(hasConflict){
|
||||
<span class="strong">Checkout via command line</span>
|
||||
<p>
|
||||
If you cannot merge a pull request automatically here, you have the option of checking
|
||||
it out via command line to resolve conflicts and perform a manual merge.
|
||||
</p>
|
||||
} else {
|
||||
<span class="strong">Merging via command line</span>
|
||||
<p>
|
||||
If you do not want to use the merge button or an automatic merge cannot be performed,
|
||||
you can perform a manual merge on the command line.
|
||||
</p>
|
||||
}
|
||||
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl, true){
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button>
|
||||
@if(settings.ssh && loginAccount.isDefined){
|
||||
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button>
|
||||
}
|
||||
</div>
|
||||
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly>
|
||||
}
|
||||
<div>
|
||||
<p>
|
||||
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes.
|
||||
</p>
|
||||
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
|
||||
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
|
||||
@helper.html.copy("merge-command-copy-1", command){
|
||||
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<span class="strong">Step 2:</span> Merge the changes and update on the server.
|
||||
</p>
|
||||
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
|
||||
s"git push origin ${pullreq.branch}"){ command =>
|
||||
@helper.html.copy("merge-command-copy-2", command){
|
||||
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;">@command</pre>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirm-merge-form" style="display: none;">
|
||||
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge">
|
||||
@@ -153,8 +134,8 @@
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
$('.show-command-line').click(function(){
|
||||
$('#command-line').toggle();
|
||||
$('#show-command-line').click(function(){
|
||||
$('#command-line').show();
|
||||
return false;
|
||||
});
|
||||
function setToggleAllChecksLabel(){
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
||||
hasWritePermission: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
flash: Map[String, String])(implicit context: gitbucket.core.controller.Context)
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@import gitbucket.core.model._
|
||||
@@ -74,12 +73,6 @@
|
||||
</ul>
|
||||
<div class="tab-content fill-width pull-left">
|
||||
<div class="tab-pane active" id="conversation">
|
||||
@flash.get("error").map{ error =>
|
||||
<div class="alert alert-error">@error</div>
|
||||
}
|
||||
@flash.get("info").map{ info =>
|
||||
<div class="alert alert-info">@info</div>
|
||||
}
|
||||
@pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
</div>
|
||||
<div class="tab-pane" id="commits">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
isBlame: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(s"${(repository.name :: pathList).mkString("/")} at ${encodeRefName(branch)} - ${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@html.menu("code", repository){
|
||||
<div class="head">
|
||||
<div class="pull-right hide-if-blame"><div class="btn-group">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(branchInfo: Seq[(gitbucket.core.util.JGitUtil.BranchInfo, Option[(gitbucket.core.model.PullRequest, gitbucket.core.model.Issue)], Boolean)],
|
||||
@(branchInfo: Seq[(gitbucket.core.util.JGitUtil.BranchInfo, Option[(gitbucket.core.model.PullRequest, gitbucket.core.model.Issue)])],
|
||||
hasWritePermission: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@@ -13,7 +13,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@branchInfo.map { case (branch, prs, isProtected) =>
|
||||
@branchInfo.map { case (branch, prs) =>
|
||||
<tr><td style="padding: 12px;">
|
||||
<div class="branch-action">
|
||||
@branch.mergeInfo.map{ info =>
|
||||
@@ -48,18 +48,13 @@
|
||||
@if(prs.map(!_._2.closed).getOrElse(false)){
|
||||
<a class="disabled" data-toggle="tooltip" title="You can’t delete this branch because it has an open pull request"><i class="octicon octicon-trashcan"></i></a>
|
||||
} else {
|
||||
@if(isProtected){
|
||||
<a class="disabled" data-toggle="tooltip" title="You can’t delete a protected branch."><i class="octicon octicon-trashcan"></i></a>
|
||||
} else {
|
||||
<a href="@url(repository)/delete/@encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged){ data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged){warning} else {danger}"></i></a>
|
||||
}
|
||||
<a href="@url(repository)/delete/@encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged){ data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged){warning} else {danger}"></i></a>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="branch-details">
|
||||
@if(isProtected){ <span class="octicon octicon-shield" title="This branch is protected"></span> }
|
||||
<a href="@url(repository)/tree/@encodeRefName(branch.name)" class="branch-name">@branch.name</a>
|
||||
<span class="branch-meta">
|
||||
<span>Updated @helper.html.datetimeago(branch.commitTime, false)
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
pathList: List[String],
|
||||
fileName: Option[String],
|
||||
content: gitbucket.core.util.JGitUtil.ContentInfo,
|
||||
protectedBranch: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
content: gitbucket.core.util.JGitUtil.ContentInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(if(fileName.isEmpty) "New File" else s"Editing ${fileName.get} at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@html.menu("code", repository){
|
||||
@if(protectedBranch){
|
||||
<div class="alert alert-danger">branch @branch is protected.</div>
|
||||
}
|
||||
<form method="POST" action="@url(repository)/@if(fileName.isEmpty){create}else{update}" validate="true">
|
||||
<span class="error" id="error-newFileName"></span>
|
||||
<div class="head">
|
||||
@@ -86,9 +82,6 @@ $(function(){
|
||||
@if(fileName.isDefined){
|
||||
editor.getSession().setMode("ace/mode/@editorType(fileName.get)");
|
||||
}
|
||||
@if(protectedBranch){
|
||||
editor.setReadOnly(true);
|
||||
}
|
||||
|
||||
editor.on('change', function(){
|
||||
updateCommitButtonStatus();
|
||||
@@ -129,17 +122,16 @@ $(function(){
|
||||
$('#editor').hide();
|
||||
$('#preview').show();
|
||||
$('#btn-code').removeClass('active');
|
||||
$('#btn-preview').addClass('active');
|
||||
$('#btn-preview').appendClass('active');
|
||||
|
||||
@if(fileName.map(isRenderable _).getOrElse(false)) {
|
||||
// update preview
|
||||
$('#preview').html('<img src="@assets/common/images/indicator.gif"> Previewing...');
|
||||
$.post('@url(repository)/_preview', {
|
||||
content : editor.getValue(),
|
||||
enableWikiLink : false,
|
||||
enableRefsLink : false,
|
||||
enableLineBreaks : false,
|
||||
enableTaskList : false
|
||||
content : editor.getValue(),
|
||||
enableWikiLink : false,
|
||||
enableRefsLink : false,
|
||||
enableTaskList : false
|
||||
}, function(data){
|
||||
$('#preview').empty().append(
|
||||
$('<div class="markdown-body" style="padding-left: 20px; padding-right: 20px;">').html(data));
|
||||
|
||||
@@ -11,16 +11,7 @@
|
||||
error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(
|
||||
if(pathList.isEmpty){
|
||||
if(branch == repository.repository.defaultBranch){
|
||||
s"${repository.owner}/${repository.name}"
|
||||
} else {
|
||||
s"${repository.owner}/${repository.name} at ${encodeRefName(branch)}"
|
||||
}
|
||||
} else {
|
||||
s"${(repository.name :: pathList).mkString("/")} at ${encodeRefName(branch)} - ${repository.owner}/${repository.name}"
|
||||
}, Some(repository)) {
|
||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@html.menu("code", repository, Some(branch), pathList.isEmpty, groupNames.isEmpty, info, error){
|
||||
<div class="head">
|
||||
<div class="pull-right"><div class="btn-group">
|
||||
@@ -106,25 +97,25 @@
|
||||
}
|
||||
</td>
|
||||
<td class="content-name">
|
||||
@if(file.isDirectory){
|
||||
@if(file.linkUrl.isDefined){
|
||||
<a href="@file.linkUrl">
|
||||
<span class="simplified-path">@file.name.split("/").toList.init match {
|
||||
case Nil => {}
|
||||
case list => {@list.mkString("", "/", "/")}
|
||||
}</span>@file.name.split("/").toList.last
|
||||
</a>
|
||||
} else {
|
||||
<a href="@url(repository)/tree@{(branch :: pathList).map(encodeRefName).mkString("/", "/", "/")}@{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>
|
||||
}
|
||||
@if(file.isDirectory){
|
||||
@if(file.linkUrl.isDefined){
|
||||
<a href="@file.linkUrl">
|
||||
<span class="simplified-path">@file.name.split("/").toList.init match {
|
||||
case Nil => {}
|
||||
case list => {@list.mkString("", "/", "/")}
|
||||
}</span>@file.name.split("/").toList.last
|
||||
</a>
|
||||
} else {
|
||||
<a href="@url(repository)/blob@{(encodeRefName(branch) :: pathList).mkString("/", "/", "/")}@file.name">@file.name</a>
|
||||
<a href="@url(repository)/tree@{(encodeRefName(branch) :: pathList).mkString("/", "/", "/")}@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 {
|
||||
<a href="@url(repository)/blob@{(encodeRefName(branch) :: pathList).mkString("/", "/", "/")}@file.name">@file.name</a>
|
||||
}
|
||||
</td>
|
||||
<td class="mute">
|
||||
<a href="@url(repository)/commit/@file.commitId" class="commit-message shorten-text" title="@file.message">@link(file.message, repository)</a>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
protectedBranchList: Seq[String],
|
||||
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@import gitbucket.core.model.WebHook._
|
||||
@html.main("Branches", Some(repository)){
|
||||
@html.menu("settings", repository){
|
||||
@menu("branches", repository){
|
||||
@if(repository.branchList.isEmpty){
|
||||
<div class="well">
|
||||
<center>
|
||||
<p><i class="octicon octicon-git-branch" style="font-size:300%"></i></p>
|
||||
<p>You don’t have any branches</p>
|
||||
<p>Before you can edit branch settings, you need to add a branch.</p>
|
||||
</center>
|
||||
</div>
|
||||
}else{
|
||||
@helper.html.information(info)
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Default branch</div>
|
||||
<div class="panel-body">
|
||||
<p>The default branch is considered the “base” branch in your repository, against which all pull requests and code commits are automatically made, unless you specify a different branch.</p>
|
||||
<form id="form" method="post" action="@url(repository)/settings/update_default_branch" validate="true" class="form-inline">
|
||||
<span class="error" id="error-defaultBranch"></span>
|
||||
<select name="defaultBranch" id="defaultBranch" class="form-control">
|
||||
@repository.branchList.map { branch =>
|
||||
<option @if(branch==repository.repository.defaultBranch){ selected}>@branch</option>
|
||||
}
|
||||
</select>
|
||||
<input type="submit" class="btn btn-default" value="Update" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Protected branches</div>
|
||||
<div class="panel-body">
|
||||
<p>Protect branches to disable force pushing, prevent branches from being deleted, and optionally require status checks before merging. New to protected branches?
|
||||
<form class="form-inline">
|
||||
<select name="protectBranch" id="protectBranch" onchange="location=$(this).val()" class="form-control">
|
||||
<option>Choose a branch...</option>
|
||||
@repository.branchList.map { branch =>
|
||||
<option value="@url(repository)/settings/branches/@encodeRefName(branch)">@branch</option>
|
||||
}
|
||||
</select>
|
||||
<span class="error" id="error-protectBranch"></span>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
<table class="table table-bordered table-hover branches">
|
||||
@protectedBranchList.map { branch =>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="branch-name">@branch</span>
|
||||
<span class="branch-action">
|
||||
<a href="@url(repository)/settings/branches/@encodeRefName(branch)" class="btn btn-small btn-default">Edit</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
protection: gitbucket.core.api.ApiBranchProtection,
|
||||
knownContexts: Seq[String],
|
||||
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@import gitbucket.core.model.WebHook._
|
||||
@check(bool:Boolean)={@if(bool){ checked}}
|
||||
@html.main(s"Branch protection for ${branch}", Some(repository)){
|
||||
@html.menu("settings", repository){
|
||||
@menu("branches", repository){
|
||||
@helper.html.information(info)
|
||||
<div class="alert alert-info" style="display:none" id="saved-info">Branch protection options saved</div>
|
||||
<form name="branchProtection" onsubmit="submitForm(event)">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Branch protection for <b>@branch</b></div>
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="enabled" onclick="update()" @check(protection.enabled)>
|
||||
<span class="strong">Protect this branch</span>
|
||||
</label>
|
||||
<p class="help-block">Disables force-pushes to this branch and prevents it from being deleted.</p>
|
||||
</div>
|
||||
|
||||
<div class="checkbox js-enabled" style="display:none">
|
||||
<label>
|
||||
<input type="checkbox" name="has_required_statuses" onclick="update()" @check(protection.status.enforcement_level.name!="off")>
|
||||
<span class="strong">Require status checks to pass before merging</span>
|
||||
</label>
|
||||
<p class="help-block">Choose which status checks must pass before branches can be merged into test.
|
||||
When enabled, commits must first be pushed to another branch, then merged or pushed directly to test after status checks have passed.</p>
|
||||
|
||||
<div class="js-has_required_statuses" style="display:none">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="enforce_for_admins" onclick="update()" @check(protection.status.enforcement_level.name=="everyone")>
|
||||
<span class="strong">Include administrators</span>
|
||||
</label>
|
||||
<p class="help-block">Enforce required status checks for repository administrators.</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Status checks found in the last week for this repository</div>
|
||||
<div class="panel-body">
|
||||
@knownContexts.map { context =>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="contexts" value="@context" onclick="update()" @check(protection.status.contexts.find(_ == context))>
|
||||
<span>@context</span>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input class="btn btn-success btn-lg" type="submit" value="Save changes" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
function getValue(){
|
||||
var v = {}, contexts=[];
|
||||
$("input[type=checkbox]:checked").each(function(){
|
||||
if(this.name === 'contexts'){
|
||||
contexts.push(this.value);
|
||||
} else {
|
||||
v[this.name] = true;
|
||||
}
|
||||
});
|
||||
if(v.enabled){
|
||||
return {
|
||||
enabled: true,
|
||||
required_status_checks: {
|
||||
enforcement_level: v.has_required_statuses ? ((v.enforce_for_admins ? 'everyone' : 'non_admins')) : 'off',
|
||||
contexts: v.has_required_statuses ? contexts : []
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
enabled: false,
|
||||
required_status_checks: {
|
||||
enforcement_level: "off",
|
||||
contexts: []
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
function updateView(protection){
|
||||
$('.js-enabled').toggle(protection.enabled);
|
||||
$('.js-has_required_statuses').toggle(protection.required_status_checks.enforcement_level != 'off');
|
||||
}
|
||||
function update(){
|
||||
var protection = getValue();
|
||||
updateView(protection);
|
||||
}
|
||||
$(update);
|
||||
function submitForm(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var protection = getValue();
|
||||
$.ajax({
|
||||
method:'PATCH',
|
||||
url:'/api/v3/repos/@repository.owner/@repository.name/branches/@encodeRefName(branch)',
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data:JSON.stringify({protection:protection}),
|
||||
success:function(r){
|
||||
$('#saved-info').show();
|
||||
},
|
||||
error:function(err){
|
||||
console.log(err);
|
||||
alert('update error');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -11,11 +11,6 @@
|
||||
<li@if(active=="collaborators"){ class="active"}>
|
||||
<a href="@url(repository)/settings/collaborators">Collaborators</a>
|
||||
</li>
|
||||
@if(!repository.branchList.isEmpty){
|
||||
<li@if(active=="branches"){ class="active"}>
|
||||
<a href="@url(repository)/settings/branches">Branches</a>
|
||||
</li>
|
||||
}
|
||||
<li@if(active=="hooks"){ class="active"}>
|
||||
<a href="@url(repository)/settings/hooks">Service Hooks</a>
|
||||
</li>
|
||||
|
||||
@@ -18,6 +18,22 @@
|
||||
<label for="description" class="strong">Description:</label>
|
||||
<input type="text" name="description" id="description" class="form-control" value="@repository.repository.description"/>
|
||||
</fieldset>
|
||||
<fieldset class="margin form-group">
|
||||
<label for="defaultBranch" class="strong">Default Branch:</label>
|
||||
<select name="defaultBranch" id="defaultBranch"@if(repository.branchList.isEmpty){ disabled} class="form-control">
|
||||
@if(repository.branchList.isEmpty){
|
||||
<option value="none" selected>No Branch</option>
|
||||
} else {
|
||||
@repository.branchList.map { branch =>
|
||||
<option@if(branch==repository.repository.defaultBranch){ selected}>@branch</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@if(repository.branchList.isEmpty){
|
||||
<input type="hidden" name="defaultBranch" value="none"/>
|
||||
}
|
||||
<span class="error" id="error-defaultBranch"></span>
|
||||
</fieldset>
|
||||
<fieldset class="margin">
|
||||
<label class="radio">
|
||||
<input type="radio" name="isPrivate" value="false"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@helper.html.information(info)
|
||||
@html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<ul class="nav nav-tabs fill-width pull-left">
|
||||
<li>
|
||||
<h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(s"${if(pageName.isEmpty) "New Page" else pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<ul class="nav nav-tabs fill-width pull-left">
|
||||
<li>
|
||||
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(s"History - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<ul class="nav nav-tabs fill-width pull-left">
|
||||
<li>
|
||||
<h1 class="wiki-title">
|
||||
@if(pageName.isEmpty){
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
page: gitbucket.core.service.WikiService.WikiPageInfo,
|
||||
pages: List[String],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean,
|
||||
sidebar: Option[gitbucket.core.service.WikiService.WikiPageInfo],
|
||||
footer: Option[gitbucket.core.service.WikiService.WikiPageInfo])(implicit context: gitbucket.core.controller.Context)
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@import gitbucket.core.service.WikiService._
|
||||
@html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<ul class="nav nav-tabs fill-width pull-left">
|
||||
<li>
|
||||
<h1 class="wiki-title">@pageName</h1>
|
||||
<div class="small">
|
||||
@@ -30,13 +28,9 @@
|
||||
@defining(15){ max =>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">
|
||||
<a id="show-pages-index" href="javascript:void(0);">
|
||||
<span id="triangle-down" class="octicon octicon-triangle-down"></span>
|
||||
<span id="triangle-right" class="octicon octicon-triangle-right" style="display: none;"></span>
|
||||
<span class="strong">Pages</span> <span class="badge">@pages.length</span>
|
||||
</a>
|
||||
Pages <span class="badge">@pages.length</span>
|
||||
</div>
|
||||
<ul id="pages-index" class="list-group list-group-flush">
|
||||
<ul class="list-group list-group-flush">
|
||||
@pages.zipWithIndex.map { case (page, i) =>
|
||||
<li class="list-group-item page-link" style="border: none; @if(i > max - 1){display:none;}">
|
||||
<a href="@url(repository)/wiki/@urlEncode(page)" class="strong">@page</a>
|
||||
@@ -50,20 +44,6 @@
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
@sidebar.map { sidebarPage =>
|
||||
<div class="wiki-sidebar">
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/wiki/_Sidebar/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||
}
|
||||
@markdown(sidebarPage.content, repository, true, false, false, false, pages)
|
||||
</div>
|
||||
}.getOrElse{
|
||||
@if(hasWritePermission){
|
||||
<a class="button-link" href="@url(repository)/wiki/_Sidebar/_edit" style="text-decoration: none;">
|
||||
<div class="wiki-sidebar-dotted text-center"><i class="octicon octicon-plus"></i> Add a custom sidebar</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
<div class="small">
|
||||
<strong>Clone this wiki locally</strong>
|
||||
</div>
|
||||
@@ -89,20 +69,6 @@
|
||||
pages = pages
|
||||
)
|
||||
</div>
|
||||
@footer.map { footerPage =>
|
||||
<div class="wiki-sidebar wiki-footer">
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/wiki/_Footer/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||
}
|
||||
@markdown(footerPage.content, repository, true, false, false, false, pages)
|
||||
</div>
|
||||
}.getOrElse{
|
||||
@if(hasWritePermission){
|
||||
<a class="button-link" href="@url(repository)/wiki/_Footer/_edit" style="text-decoration: none;">
|
||||
<div class="wiki-sidebar-dotted text-center"><i class="octicon octicon-plus"></i> Add a custom footer</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -113,24 +79,6 @@ $(function(){
|
||||
$(e.target).parents('div.show-more').remove();
|
||||
});
|
||||
|
||||
$('#show-pages-index').click(function(e){
|
||||
if($('#pages-index').is(":visible")){
|
||||
$('#triangle-down').hide();
|
||||
$('#triangle-right').show();
|
||||
$('#pages-index').hide();
|
||||
} else {
|
||||
$('#triangle-right').hide();
|
||||
$('#triangle-down').show();
|
||||
$('#pages-index').show();
|
||||
}
|
||||
});
|
||||
|
||||
@sidebar.map { sidebarPage =>
|
||||
$('#pages-index').hide();
|
||||
$('#triangle-down').hide();
|
||||
$('#triangle-right').show();
|
||||
}
|
||||
|
||||
@if(settings.ssh && loginAccount.isDefined){
|
||||
$('#repository-url-http').click(function(){
|
||||
$('#repository-url').val('@httpUrl(repository)');
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("wiki", repository){
|
||||
<ul class="nav nav-tabs fill-width">
|
||||
<ul class="nav nav-tabs fill-width pull-left">
|
||||
<li>
|
||||
<h1 class="wiki-title"><span class="muted">Pages</span></h1>
|
||||
</li>
|
||||
|
||||
@@ -146,24 +146,6 @@ hr {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
pre.reset {
|
||||
/* defaults */
|
||||
display: block;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
margin: 1em 0px;
|
||||
/* bootstrap overrides */
|
||||
padding: initial;
|
||||
line-height: initial;
|
||||
color: initial;
|
||||
background-color: initial;
|
||||
border: initial;
|
||||
overflow: initial;
|
||||
word-break: initial;
|
||||
word-wrap: initial;
|
||||
border-radius: initial;
|
||||
}
|
||||
|
||||
/* ======================================================================== */
|
||||
/* Global Header */
|
||||
/* ======================================================================== */
|
||||
@@ -582,7 +564,7 @@ ul.sidemenu li {
|
||||
}
|
||||
|
||||
ul.sidemenu span.badge {
|
||||
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
ul.sidemenu a:hover i.menu-icon, ul.sidemenu i.menu-icon-active {
|
||||
@@ -631,11 +613,6 @@ ul.nav-stacked.side-menu li {
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
|
||||
a#show-pages-index {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*
|
||||
ul.nav-stacked.side-menu li span.header {
|
||||
border-top-right-radius: 3px;
|
||||
@@ -847,9 +824,6 @@ table.table-file-list .file-icon {
|
||||
|
||||
table.table-file-list .content-name {
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.table-file-list .commit-message {
|
||||
@@ -1336,74 +1310,6 @@ li.task-list-item input.task-list-item-checkbox {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.discussion-item {
|
||||
position: relative;
|
||||
margin: 15px 0 15px 79px;
|
||||
padding-left: 25px;
|
||||
}
|
||||
.discussion-item-header {
|
||||
min-height: 30px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
color: #767676;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.discussion-item-icon {
|
||||
float: left;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-top: -7px;
|
||||
margin-left: -40px;
|
||||
line-height: 28px;
|
||||
color: #767676;
|
||||
text-align: center;
|
||||
background-color: #f3f3f3;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.discussion-item-content {
|
||||
margin-left:36px;
|
||||
}
|
||||
.discussion-item-content-head{
|
||||
padding-right:20px;
|
||||
background-color: white;
|
||||
float:left
|
||||
}
|
||||
pre.reset.discussion-item-content-text{
|
||||
border-left: 1px solid rgb(238, 238, 238);
|
||||
margin: 0 0 0 5px;
|
||||
padding-left: 25px;
|
||||
}
|
||||
.discussion-item-commit .discussion-item-content {
|
||||
font-size:12px;
|
||||
}
|
||||
.discussion-item-icon .octicon {
|
||||
color: inherit;
|
||||
}
|
||||
.discussion-item-merge .discussion-item-icon {
|
||||
background-color: #6e5494;
|
||||
color: white;
|
||||
padding-top: 1px;
|
||||
padding-left: 3px;
|
||||
}
|
||||
.discussion-item-close .discussion-item-icon {
|
||||
background-color: #bd2c00;
|
||||
color: white;
|
||||
padding-top: 1px;
|
||||
}
|
||||
.discussion-item-delete_branch .discussion-item-icon {
|
||||
padding-left: 2px;
|
||||
padding-top: 1px;
|
||||
background-color: #767676;
|
||||
color: white;
|
||||
}
|
||||
.discussion-item-reopen .discussion-item-icon {
|
||||
background-color: #6cc644;
|
||||
padding-top: 1px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Pull Request */
|
||||
/****************************************************************************/
|
||||
@@ -1425,59 +1331,6 @@ a.absent {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
/*
|
||||
div.wiki-index-header {
|
||||
background-color: #f5f5f5;
|
||||
color: #333333;
|
||||
margin: 0;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
border: 1px solid #d8d8d8;
|
||||
padding: 8px 8px 8px 8px;
|
||||
}
|
||||
*/
|
||||
|
||||
div.wiki-sidebar {
|
||||
background-color: white;
|
||||
border: 1px solid #d8d8d8;
|
||||
padding: 8px 10px 0px 10px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
div.wiki-sidebar img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
div.wiki-sidebar-dotted {
|
||||
background-color: white;
|
||||
border: 1px dashed #ddd;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
border-radius: 3px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
div.wiki-footer {
|
||||
margin-top: 50px;
|
||||
background-color: #f5f5f5;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
/*
|
||||
div.wiki-index-content {
|
||||
background-color: white;
|
||||
border: 1px solid #d8d8d8;
|
||||
padding: 0px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
margin-bottom: 20px;
|
||||
border-top: none;
|
||||
}
|
||||
*/
|
||||
|
||||
/****************************************************************************/
|
||||
/* Commit */
|
||||
/****************************************************************************/
|
||||
@@ -1498,43 +1351,13 @@ div.author-info div.committer {
|
||||
margin: -10px -10px 10px -10px;
|
||||
}
|
||||
.build-statuses .build-status-item{
|
||||
padding: 10px 15px 10px 64px;
|
||||
padding: 10px 15px 10px 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.build-statuses-list .build-status-item{
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.merge-indicator{
|
||||
float:left;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.merge-indicator-success{
|
||||
background-color: #6cc644;
|
||||
}
|
||||
.merge-indicator-warning{
|
||||
background-color: #cea61b;
|
||||
}
|
||||
.merge-indicator-alert{
|
||||
background-color: #888;
|
||||
}
|
||||
.merge-indicator .octicon{
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
.merge-indicator-warning .octicon{
|
||||
color: white;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* Diff */
|
||||
/****************************************************************************/
|
||||
@@ -1838,39 +1661,28 @@ div.markdown-body h1 {
|
||||
border-bottom: 1px solid #ddd;
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
div.markdown-body h2 {
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
div.markdown-body h3 {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
div.markdown-body h4 {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
div.markdown-body h5 {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
div.markdown-body h6 {
|
||||
color:#777;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
div.markdown-body li {
|
||||
@@ -1882,10 +1694,6 @@ div.markdown-body p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.markdown-body img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
div.markdown-body pre {
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 12px;
|
||||
@@ -2040,19 +1848,43 @@ div.markdown-body table colgroup + tbody tr:first-child td:last-child {
|
||||
|
||||
.markdown-head {
|
||||
position: relative;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
a.markdown-anchor-link {
|
||||
margin-left: -16px;
|
||||
margin-right: 2px;
|
||||
line-height: 1;
|
||||
position: absolute;
|
||||
left: -18px;
|
||||
display: none;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
/* From octicon style */
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
}
|
||||
a.markdown-anchor-link:before { content: '\f05c'} /* */
|
||||
|
||||
h1 a.markdown-anchor-link {
|
||||
top: 24px;
|
||||
}
|
||||
|
||||
a.markdown-anchor-link span.octicon {
|
||||
visibility: hidden;
|
||||
vertical-align: middle;
|
||||
h2 a.markdown-anchor-link {
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
h3 a.markdown-anchor-link {
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
h4 a.markdown-anchor-link {
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
h5 a.markdown-anchor-link {
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
h6 a.markdown-anchor-link {
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
|
||||
@@ -19,11 +19,14 @@ $(function(){
|
||||
});
|
||||
|
||||
// anchor icon for markdown
|
||||
$('.markdown-head').on('mouseenter', function(e){
|
||||
$(this).find('span.octicon').css('visibility', 'visible');
|
||||
$('.markdown-head').mouseenter(function(e){
|
||||
$(e.target).children('a.markdown-anchor-link').show();
|
||||
});
|
||||
$('.markdown-head').on('mouseleave', function(e){
|
||||
$(this).find('span.octicon').css('visibility', 'hidden');
|
||||
$('.markdown-head').mouseleave(function(e){
|
||||
$(e.target).children('a.markdown-anchor-link').hide();
|
||||
});
|
||||
$('a.markdown-anchor-link').mouseleave(function(e){
|
||||
$(e.target).hide();
|
||||
});
|
||||
|
||||
// syntax highlighting by google-code-prettify
|
||||
@@ -603,7 +606,7 @@ var imageDiff ={
|
||||
.css({marginTop:size.height-5});
|
||||
var bar = $('<hr class="diff-silde-bar">').css({top:size.height+size.paddingTop});
|
||||
var div = $('<div class="diff-image-stack">')
|
||||
.css({height:size.height+size.paddingTop*2, paddingLeft:size.padding})
|
||||
.css({height:size.height+size.paddingTop, paddingLeft:size.padding})
|
||||
.append(diffOld, diffNew, bar, handle);
|
||||
return {
|
||||
neo:diffNew,
|
||||
|
||||
@@ -209,15 +209,6 @@ class JsonFormatSpec extends Specification {
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/$sha1/status"
|
||||
}"""
|
||||
|
||||
val apiLabel = ApiLabel(
|
||||
name = "bug",
|
||||
color = "f29513")(RepositoryName("octocat","Hello-World"))
|
||||
val apiLabelJson = s"""{
|
||||
"name": "bug",
|
||||
"color": "f29513",
|
||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/labels/bug"
|
||||
}"""
|
||||
|
||||
val apiIssue = ApiIssue(
|
||||
number = 1347,
|
||||
title = "Found a bug",
|
||||
@@ -363,16 +354,6 @@ class JsonFormatSpec extends Specification {
|
||||
}"""
|
||||
|
||||
|
||||
val apiBranchProtection = ApiBranchProtection(true, Some(ApiBranchProtection.Status(ApiBranchProtection.Everyone, Seq("continuous-integration/travis-ci"))))
|
||||
val apiBranchProtectionJson = """{
|
||||
"enabled": true,
|
||||
"required_status_checks": {
|
||||
"enforcement_level": "everyone",
|
||||
"contexts": [
|
||||
"continuous-integration/travis-ci"
|
||||
]
|
||||
}
|
||||
}"""
|
||||
|
||||
def beFormatted(json2Arg:String) = new Matcher[String] {
|
||||
def apply[S <: String](e: Expectable[S]) = {
|
||||
@@ -420,9 +401,6 @@ class JsonFormatSpec extends Specification {
|
||||
"apiCombinedCommitStatus" in {
|
||||
JsonFormat(apiCombinedCommitStatus) must beFormatted(apiCombinedCommitStatusJson)
|
||||
}
|
||||
"apiLabel" in {
|
||||
JsonFormat(apiLabel) must beFormatted(apiLabelJson)
|
||||
}
|
||||
"apiIssue" in {
|
||||
JsonFormat(apiIssue) must beFormatted(apiIssueJson)
|
||||
JsonFormat(apiIssuePR) must beFormatted(apiIssuePRJson)
|
||||
@@ -433,8 +411,5 @@ class JsonFormatSpec extends Specification {
|
||||
"apiPullRequestReviewComment" in {
|
||||
JsonFormat(apiPullRequestReviewComment) must beFormatted(apiPullRequestReviewCommentJson)
|
||||
}
|
||||
"apiBranchProtection" in {
|
||||
JsonFormat(apiBranchProtection) must beFormatted(apiBranchProtectionJson)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
class LabelsServiceSpec extends Specification with ServiceSpecBase {
|
||||
"getLabels" should {
|
||||
"be empty when not have any labels" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||
|
||||
dummyService.getLabels("user1", "repo1") must haveSize(0)
|
||||
}}
|
||||
"return contained labels" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||
val labelId2 = dummyService.createLabel("user1", "repo1", "label2", "ffffff")
|
||||
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||
|
||||
def getLabels = dummyService.getLabels("user1", "repo1")
|
||||
|
||||
getLabels must haveSize(2)
|
||||
getLabels must_== List(
|
||||
Label("user1", "repo1", labelId1, "label1", "000000"),
|
||||
Label("user1", "repo1", labelId2, "label2", "ffffff"))
|
||||
}}
|
||||
}
|
||||
"getLabel" should {
|
||||
"return None when the label not exist" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
|
||||
dummyService.getLabel("user1", "repo1", 1) must beNone
|
||||
dummyService.getLabel("user1", "repo1", "label1") must beNone
|
||||
}}
|
||||
"return a label fetched by label id" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||
dummyService.createLabel("user1", "repo1", "label2", "ffffff")
|
||||
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||
|
||||
def getLabel = dummyService.getLabel("user1", "repo1", labelId1)
|
||||
getLabel must_== Some(Label("user1", "repo1", labelId1, "label1", "000000"))
|
||||
}}
|
||||
"return a label fetched by label name" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||
dummyService.createLabel("user1", "repo1", "label2", "ffffff")
|
||||
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||
|
||||
def getLabel = dummyService.getLabel("user1", "repo1", "label1")
|
||||
getLabel must_== Some(Label("user1", "repo1", labelId1, "label1", "000000"))
|
||||
}}
|
||||
}
|
||||
"createLabel" should {
|
||||
"return accurate label id" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||
val labelId = dummyService.createLabel("user1", "repo1", "label2", "000000")
|
||||
labelId must_== 4
|
||||
def getLabel = dummyService.getLabel("user1", "repo1", labelId)
|
||||
getLabel must_== Some(Label("user1", "repo1", labelId, "label2", "000000"))
|
||||
}}
|
||||
}
|
||||
"updateLabel" should {
|
||||
"change target label" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
val labelId = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||
dummyService.updateLabel("user1", "repo1", labelId, "updated-label", "ffffff")
|
||||
def getLabel = dummyService.getLabel("user1", "repo1", labelId)
|
||||
getLabel must_== Some(Label("user1", "repo1", labelId, "updated-label", "ffffff"))
|
||||
}}
|
||||
}
|
||||
"deleteLabel" should {
|
||||
"remove target label" in { withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
generateNewUserWithDBRepository("user1", "repo2")
|
||||
generateNewUserWithDBRepository("user2", "repo1")
|
||||
val labelId = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||
dummyService.deleteLabel("user1", "repo1", labelId)
|
||||
dummyService.getLabel("user1", "repo1", labelId) must beNone
|
||||
}}
|
||||
}
|
||||
}
|
||||
@@ -45,73 +45,73 @@ class MergeServiceSpec extends Specification {
|
||||
conflicted mustEqual false
|
||||
}
|
||||
"checkConflict true if not conflicted, and create cache" in {
|
||||
val repo2Dir = initRepository("user1","repo2")
|
||||
using(Git.open(repo2Dir)){ git =>
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createConfrict(git)
|
||||
}
|
||||
service.checkConflictCache("user1", "repo2", branch, issueId) mustEqual None
|
||||
val conflicted = service.checkConflict("user1", "repo2", branch, issueId)
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
|
||||
val conflicted = service.checkConflict("user1", "repo1", branch, issueId)
|
||||
conflicted mustEqual true
|
||||
service.checkConflictCache("user1", "repo2", branch, issueId) mustEqual Some(true)
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(true)
|
||||
}
|
||||
}
|
||||
"checkConflictCache" should {
|
||||
"merged cache invalid if origin branch moved" in {
|
||||
val repo3Dir = initRepository("user1","repo3")
|
||||
service.checkConflict("user1", "repo3", branch, issueId) mustEqual false
|
||||
service.checkConflictCache("user1", "repo3", branch, issueId) mustEqual Some(false)
|
||||
using(Git.open(repo3Dir)){ git =>
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
service.checkConflict("user1", "repo1", branch, issueId) mustEqual false
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(false)
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" )
|
||||
}
|
||||
service.checkConflictCache("user1", "repo3", branch, issueId) mustEqual None
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
|
||||
}
|
||||
"merged cache invalid if request branch moved" in {
|
||||
val repo4Dir = initRepository("user1","repo4")
|
||||
service.checkConflict("user1", "repo4", branch, issueId) mustEqual false
|
||||
service.checkConflictCache("user1", "repo4", branch, issueId) mustEqual Some(false)
|
||||
using(Git.open(repo4Dir)){ git =>
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
service.checkConflict("user1", "repo1", branch, issueId) mustEqual false
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(false)
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" )
|
||||
}
|
||||
service.checkConflictCache("user1", "repo4", branch, issueId) mustEqual None
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
|
||||
}
|
||||
"merged cache invalid if origin branch moved" in {
|
||||
val repo5Dir = initRepository("user1","repo5")
|
||||
service.checkConflict("user1", "repo5", branch, issueId) mustEqual false
|
||||
service.checkConflictCache("user1", "repo5", branch, issueId) mustEqual Some(false)
|
||||
using(Git.open(repo5Dir)){ git =>
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
service.checkConflict("user1", "repo1", branch, issueId) mustEqual false
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(false)
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" )
|
||||
}
|
||||
service.checkConflictCache("user1", "repo5", branch, issueId) mustEqual None
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
|
||||
}
|
||||
"conflicted cache invalid if request branch moved" in {
|
||||
val repo6Dir = initRepository("user1","repo6")
|
||||
using(Git.open(repo6Dir)){ git =>
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createConfrict(git)
|
||||
}
|
||||
service.checkConflict("user1", "repo6", branch, issueId) mustEqual true
|
||||
service.checkConflictCache("user1", "repo6", branch, issueId) mustEqual Some(true)
|
||||
using(Git.open(repo6Dir)){ git =>
|
||||
service.checkConflict("user1", "repo1", branch, issueId) mustEqual true
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(true)
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" )
|
||||
}
|
||||
service.checkConflictCache("user1", "repo6", branch, issueId) mustEqual None
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
|
||||
}
|
||||
"conflicted cache invalid if origin branch moved" in {
|
||||
val repo7Dir = initRepository("user1","repo7")
|
||||
using(Git.open(repo7Dir)){ git =>
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createConfrict(git)
|
||||
}
|
||||
service.checkConflict("user1", "repo7", branch, issueId) mustEqual true
|
||||
service.checkConflictCache("user1", "repo7", branch, issueId) mustEqual Some(true)
|
||||
using(Git.open(repo7Dir)){ git =>
|
||||
service.checkConflict("user1", "repo1", branch, issueId) mustEqual true
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(true)
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge4" )
|
||||
}
|
||||
service.checkConflictCache("user1", "repo7", branch, issueId) mustEqual None
|
||||
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
|
||||
}
|
||||
}
|
||||
"mergePullRequest" should {
|
||||
"can merge" in {
|
||||
val repo8Dir = initRepository("user1","repo8")
|
||||
using(Git.open(repo8Dir)){ git =>
|
||||
val repo1Dir = initRepository("user1","repo1")
|
||||
using(Git.open(repo1Dir)){ git =>
|
||||
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge2" )
|
||||
val committer = new PersonIdent("dummy2", "dummy2@example.com")
|
||||
getFile(git, branch, "test.txt").content.get mustEqual "hoge"
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.util.GitSpecUtil._
|
||||
import org.specs2.mutable.Specification
|
||||
import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand}
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import gitbucket.core.model.CommitState
|
||||
import gitbucket.core.service.ProtectedBranchService.{ProtectedBranchReceiveHook, ProtectedBranchInfo}
|
||||
import scalaz._, Scalaz._
|
||||
|
||||
class ProtectedBranchServiceSpec extends Specification with ServiceSpecBase with ProtectedBranchService with CommitStatusService {
|
||||
|
||||
val receiveHook = new ProtectedBranchReceiveHook()
|
||||
val now = new java.util.Date()
|
||||
val sha = "0c77148632618b59b6f70004e3084002be2b8804"
|
||||
val sha2 = "0c77148632618b59b6f70004e3084002be2b8805"
|
||||
|
||||
"getProtectedBranchInfo" should {
|
||||
"empty is disabled" in {
|
||||
withTestDB { implicit session =>
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo.disabled("user1", "repo1")
|
||||
}
|
||||
}
|
||||
"enable and update and disable" in {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo("user1", "repo1", true, Nil, false)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("hoge","huge"))
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo("user1", "repo1", true, Seq("hoge","huge"), true)
|
||||
disableBranchProtection("user1", "repo1", "branch")
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo.disabled("user1", "repo1")
|
||||
}
|
||||
}
|
||||
"empty contexts is no-include-administrators" in {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators must_== false
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Nil)
|
||||
getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators must_== false
|
||||
}
|
||||
}
|
||||
"getProtectedBranchList" in {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch2", false, Seq("fuga"))
|
||||
enableBranchProtection("user1", "repo1", "branch3", true, Seq("hoge"))
|
||||
getProtectedBranchList("user1", "repo1").toSet must_== Set("branch", "branch2", "branch3")
|
||||
}
|
||||
}
|
||||
"getBranchProtectedReason on force push from admin" in {
|
||||
withTestDB { implicit session =>
|
||||
withTestRepository { git =>
|
||||
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(true) }
|
||||
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE_NONFASTFORWARD)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("Cannot force-push to a protected branch")
|
||||
}
|
||||
}
|
||||
}
|
||||
"getBranchProtectedReason on force push from othre" in {
|
||||
withTestDB { implicit session =>
|
||||
withTestRepository { git =>
|
||||
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(true) }
|
||||
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE_NONFASTFORWARD)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== None
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("Cannot force-push to a protected branch")
|
||||
}
|
||||
}
|
||||
}
|
||||
"getBranchProtectedReason check status on push from othre" in {
|
||||
withTestDB { implicit session =>
|
||||
withTestRepository { git =>
|
||||
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(false) }
|
||||
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE)
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== None
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("Required status check \"must\" is expected")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("2 of 2 required status checks are expected")
|
||||
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("2 of 2 required status checks are expected")
|
||||
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("Required status check \"must2\" is expected")
|
||||
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== None
|
||||
}
|
||||
}
|
||||
}
|
||||
"getBranchProtectedReason check status on push from admin" in {
|
||||
withTestDB { implicit session =>
|
||||
withTestRepository { git =>
|
||||
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(false) }
|
||||
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE)
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("must"))
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("Required status check \"must\" is expected")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("must", "must2"))
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("2 of 2 required status checks are expected")
|
||||
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("2 of 2 required status checks are expected")
|
||||
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("Required status check \"must2\" is expected")
|
||||
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"ProtectedBranchInfo" should {
|
||||
"administrator is owner" in {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", true, Nil, false)
|
||||
x.isAdministrator("user1") must_== true
|
||||
x.isAdministrator("user2") must_== false
|
||||
}
|
||||
}
|
||||
"administrator is manager" in {
|
||||
withTestDB { implicit session =>
|
||||
val x = ProtectedBranchInfo("grp1", "repo1", true, Nil, false)
|
||||
x.createGroup("grp1", None)
|
||||
generateNewAccount("user1")
|
||||
generateNewAccount("user2")
|
||||
generateNewAccount("user3")
|
||||
|
||||
x.updateGroupMembers("grp1", List("user1"->true, "user2"->false))
|
||||
x.isAdministrator("user1") must_== true
|
||||
x.isAdministrator("user2") must_== false
|
||||
x.isAdministrator("user3") must_== false
|
||||
}
|
||||
}
|
||||
"unSuccessedContexts" in {
|
||||
withTestDB { implicit session =>
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", true, List("must"), false)
|
||||
x.unSuccessedContexts(sha) must_== Set("must")
|
||||
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
x.unSuccessedContexts(sha) must_== Set("must")
|
||||
createCommitStatus("user1", "repo1", sha, "must", CommitState.ERROR, None, None, now, user1)
|
||||
x.unSuccessedContexts(sha) must_== Set("must")
|
||||
createCommitStatus("user1", "repo1", sha, "must", CommitState.PENDING, None, None, now, user1)
|
||||
x.unSuccessedContexts(sha) must_== Set("must")
|
||||
createCommitStatus("user1", "repo1", sha, "must", CommitState.FAILURE, None, None, now, user1)
|
||||
x.unSuccessedContexts(sha) must_== Set("must")
|
||||
createCommitStatus("user1", "repo1", sha, "must", CommitState.SUCCESS, None, None, now, user1)
|
||||
x.unSuccessedContexts(sha) must_== Set()
|
||||
}
|
||||
}
|
||||
"unSuccessedContexts when empty" in {
|
||||
withTestDB { implicit session =>
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", true, Nil, false)
|
||||
val sha = "0c77148632618b59b6f70004e3084002be2b8804"
|
||||
x.unSuccessedContexts(sha) must_== Nil
|
||||
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
x.unSuccessedContexts(sha) must_== Nil
|
||||
}
|
||||
}
|
||||
"if disabled, needStatusCheck is false" in {
|
||||
withTestDB { implicit session =>
|
||||
ProtectedBranchInfo("user1", "repo1", false, Seq("must"), true).needStatusCheck("user1") must_== false
|
||||
}
|
||||
}
|
||||
"needStatusCheck includeAdministrators" in {
|
||||
withTestDB { implicit session =>
|
||||
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), false).needStatusCheck("user2") must_== true
|
||||
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), false).needStatusCheck("user1") must_== false
|
||||
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), true ).needStatusCheck("user2") must_== true
|
||||
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), true ).needStatusCheck("user1") must_== true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.Profile._
|
||||
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
|
||||
class RepositoryServiceSpec extends Specification with ServiceSpecBase with RepositoryService with AccountService{
|
||||
"RepositoryService" should {
|
||||
"renameRepository can rename CommitState, ProtectedBranches" in { withTestDB { implicit session =>
|
||||
"renameRepository can rename CommitState" in { withTestDB { implicit session =>
|
||||
val tester = generateNewAccount("tester")
|
||||
createRepository("repo","root",None,false)
|
||||
val service = new CommitStatusService with ProtectedBranchService {}
|
||||
val id = service.createCommitStatus(
|
||||
val commitStatusService = new CommitStatusService{}
|
||||
val id = commitStatusService.createCommitStatus(
|
||||
userName = "root",
|
||||
repositoryName = "repo",
|
||||
sha = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7",
|
||||
@@ -20,20 +22,14 @@ class RepositoryServiceSpec extends Specification with ServiceSpecBase with Repo
|
||||
description = Some("description"),
|
||||
creator = tester,
|
||||
now = new java.util.Date)
|
||||
service.enableBranchProtection("root", "repo", "branch", true, Seq("must1", "must2"))
|
||||
var orgPbi = service.getProtectedBranchInfo("root", "repo", "branch")
|
||||
val org = service.getCommitStatus("root","repo", id).get
|
||||
|
||||
val org = commitStatusService.getCommitStatus("root","repo", id).get
|
||||
renameRepository("root","repo","tester","repo2")
|
||||
|
||||
val neo = service.getCommitStatus("tester","repo2", org.commitId, org.context).get
|
||||
val neo = commitStatusService.getCommitStatus("tester","repo2", org.commitId, org.context).get
|
||||
neo must_==
|
||||
org.copy(
|
||||
commitStatusId=neo.commitStatusId,
|
||||
repositoryName="repo2",
|
||||
userName="tester")
|
||||
service.getProtectedBranchInfo("tester", "repo2", "branch") must_==
|
||||
orgPbi.copy(owner="tester", repository="repo2")
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user