Compare commits

..

3 Commits
3.11 ... 3.10.1

Author SHA1 Message Date
Naoki Takezoe
89f3e66785 Update version number to 3.10.1 2016-01-01 14:28:42 +09:00
Naoki Takezoe
61670ddc9e Fix dropdown bug 2016-01-01 14:23:28 +09:00
Naoki Takezoe
1ddcc36778 Remove file appender configuration 2016-01-01 13:21:58 +09:00
102 changed files with 767 additions and 2405 deletions

View File

@@ -9,21 +9,28 @@ The current version of GitBucket provides a basic features below:
- Public / Private Git repository (http and ssh access) - Public / Private Git repository (http and ssh access)
- Repository viewer and online file editing - Repository viewer and online file editing
- Repository search (Code and Issues)
- Wiki - Wiki
- Issues / Pull request - Issues
- Fork / Pull request
- Email notification - Email notification
- Activity timeline
- Simple user and group management with LDAP integration - Simple user and group management with LDAP integration
- Gravatar support
- Plug-in system - 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 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). 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. 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. 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. 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 Plug-ins
-------- --------
@@ -60,18 +95,10 @@ Support
Release Notes 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 ### 3.10 - 30 Dec 2015
- Move to Bootstrap3 - Move to Bootstrap3
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`) - 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 - Update H2 database
### 3.9 - 5 Dec 2015 ### 3.9 - 5 Dec 2015
@@ -315,3 +342,7 @@ Release Notes
### 1.0 - 04 Jul 2013 ### 1.0 - 04 Jul 2013
- This is a first public release - This is a first public release
Sponsors
--------
[![IntelliJ IDEA](https://www.jetbrains.com/idea/docs/logo_intellij_idea.png)](https://www.jetbrains.com/idea/)

159
build.sbt
View File

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

View File

@@ -8,7 +8,7 @@ To release a new version of GitBucket, add the version definition to the [gitbuc
object AutoUpdate { 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( val versions = Seq(
Version(1, 0) 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. 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 esources other than database. Override ```Version.update``` like below:
```scala ```scala
val versions = Seq( val versions = Seq(

View File

@@ -8,10 +8,10 @@ This directory has following structure:
* /HOME/gitbucket * /HOME/gitbucket
* /repositories * /repositories
* /USER_NAME * /USER_NAME
* /REPO_NAME.git (substance of repository. GitServlet sees this directory) * / REPO_NAME.git (substance of repository. GitServlet sees this directory)
* /REPO_NAME * / REPO_NAME
* /issues (files which are attached to issue) * /issues (files which are attached to issue)
* /REPO_NAME.wiki.git (wiki repository) * / REPO_NAME.wiki.git (wiki repository)
* /data * /data
* /USER_NAME * /USER_NAME
* /files * /files

View File

@@ -27,8 +27,7 @@ $ sbt package
To build executable war file, run To build executable war file, run
``` * Windows: Not available
$ sbt executable * 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.

View File

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

View File

@@ -9,4 +9,3 @@ Developer's Guide
* [Notification Email](notification.md) * [Notification Email](notification.md)
* [Automatic Schema Updating](auto_update.md) * [Automatic Schema Updating](auto_update.md)
* [Release Operation](release.md) * [Release Operation](release.md)
* [JRebel integration (optional)](jrebel.md)

View File

@@ -23,7 +23,7 @@ object MyBuild extends Build {
object AutoUpdate { 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( val versions = Seq(
new Version(3, 3), // <---- add this line!! 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 ### 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 ```bash
$sbt executable $ cd release
$ ./make-release-war.sh
``` ```
### Deploy assembly jar file ### Deploy assembly jar file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

11
embed-jetty/update.sh Executable file
View 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
View 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.190",
"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)
}

View File

@@ -2,5 +2,4 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4") addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0") addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")

67
release/build.xml Normal file
View 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>

View File

@@ -5,15 +5,6 @@ cd ../
./sbt.sh clean assembly ./sbt.sh clean assembly
cd release 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 \ mvn deploy:deploy-file \
-DgroupId=gitbucket\ -DgroupId=gitbucket\
-DartifactId=gitbucket-assembly\ -DartifactId=gitbucket-assembly\
@@ -21,4 +12,4 @@ mvn deploy:deploy-file \
-Dpackaging=jar\ -Dpackaging=jar\
-Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\ -Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\
-DrepositoryId=sourceforge.jp\ -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
View 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
)

View File

@@ -1,4 +1,5 @@
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File; import java.io.File;
@@ -29,16 +30,16 @@ public class JettyLauncher {
} }
} }
Server server = new Server(port); Server server = new Server();
// SelectChannelConnector connector = new SelectChannelConnector(); SelectChannelConnector connector = new SelectChannelConnector();
// if(host != null) { if(host != null) {
// connector.setHost(host); connector.setHost(host);
// } }
// connector.setMaxIdleTime(1000 * 60 * 60); connector.setMaxIdleTime(1000 * 60 * 60);
// connector.setSoLingerTime(-1); connector.setSoLingerTime(-1);
// connector.setPort(port); connector.setPort(port);
// server.addConnector(connector); server.addConnector(connector);
WebAppContext context = new WebAppContext(); WebAppContext context = new WebAppContext();

View File

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

View File

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

View File

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

View File

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

View File

@@ -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+_.]+")
}
}

View File

@@ -30,9 +30,7 @@ object JsonFormat {
FieldSerializer[ApiCombinedCommitStatus]() + FieldSerializer[ApiCombinedCommitStatus]() +
FieldSerializer[ApiPullRequest.Commit]() + FieldSerializer[ApiPullRequest.Commit]() +
FieldSerializer[ApiIssue]() + FieldSerializer[ApiIssue]() +
FieldSerializer[ApiComment]() + FieldSerializer[ApiComment]()
FieldSerializer[ApiLabel]() +
ApiBranchProtection.enforcementLevelSerializer
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format => def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
( (

View File

@@ -12,7 +12,7 @@ import gitbucket.core.util.Implicits._
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil._
import gitbucket.core.util._ import gitbucket.core.util._
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache import org.eclipse.jgit.dircache.DirCache

View File

@@ -8,7 +8,7 @@ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util._ import gitbucket.core.util._
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.json4s._ import org.json4s._
import org.scalatra._ import org.scalatra._
@@ -28,11 +28,7 @@ abstract class ControllerBase extends ScalatraFilter
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
with SystemSettingsService { with SystemSettingsService {
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats implicit val jsonFormats = DefaultFormats
before("/api/v3/*") {
contentType = formats("json")
}
// TODO Scala 2.11 // TODO Scala 2.11
// // Don't set content type via Accept header. // // Don't set content type via Accept header.

View File

@@ -8,7 +8,7 @@ import gitbucket.core.service.{RepositoryService, ActivityService, AccountServic
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator} import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
class IndexController extends IndexControllerBase class IndexController extends IndexControllerBase

View File

@@ -11,7 +11,7 @@ import gitbucket.core.util._
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.Markdown import gitbucket.core.view.Markdown
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.scalatra.Ok import org.scalatra.Ok

View File

@@ -1,13 +1,12 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api.{ApiError, CreateALabel, ApiLabel, JsonFormat}
import gitbucket.core.issues.labels.html import gitbucket.core.issues.labels.html
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService} 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 gitbucket.core.util.Implicits._
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.scalatra.{NoContent, UnprocessableEntity, Created, Ok} import org.scalatra.Ok
class LabelsController extends LabelsControllerBase class LabelsController extends LabelsControllerBase
with LabelsService with IssuesService with RepositoryService with AccountService with LabelsService with IssuesService with RepositoryService with AccountService
@@ -20,7 +19,7 @@ trait LabelsControllerBase extends ControllerBase {
case class LabelForm(labelName: String, color: String) case class LabelForm(labelName: String, color: String)
val labelForm = mapping( 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))) "labelColor" -> trim(label("Color", text(required, color)))
)(LabelForm.apply) )(LabelForm.apply)
@@ -32,26 +31,6 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount)) 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 => ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
html.edit(None, repository) html.edit(None, repository)
}) })
@@ -66,31 +45,6 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount)) 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 => ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label => getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
html.edit(Some(label), repository) html.edit(Some(label), repository)
@@ -107,50 +61,11 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount)) 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 => ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
deleteLabel(repository.owner, repository.name, params("labelId").toInt) deleteLabel(repository.owner, repository.name, params("labelId").toInt)
Ok() 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. * 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.")
}
}
} }

View File

@@ -4,7 +4,7 @@ import gitbucket.core.issues.milestones.html
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService} import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator} import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
class MilestonesController extends MilestonesControllerBase class MilestonesController extends MilestonesControllerBase
with MilestonesService with RepositoryService with AccountService with MilestonesService with RepositoryService with AccountService

View File

@@ -1,7 +1,7 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api._ 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.pulls.html
import gitbucket.core.service.CommitStatusService import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService import gitbucket.core.service.MergeService
@@ -16,7 +16,7 @@ import gitbucket.core.util._
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers 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.api.Git
import org.eclipse.jgit.lib.PersonIdent import org.eclipse.jgit.lib.PersonIdent
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -27,13 +27,13 @@ import scala.collection.JavaConverters._
class PullRequestsController extends PullRequestsControllerBase class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
with CommitStatusService with MergeService with ProtectedBranchService with CommitStatusService with MergeService
trait PullRequestsControllerBase extends ControllerBase { trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator 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]) private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
@@ -119,8 +119,7 @@ trait PullRequestsControllerBase extends ControllerBase {
commits, commits,
diffs, diffs,
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
repository, repository)
flash.toMap.map(f => f._1 -> f._2.toString))
} }
} }
} getOrElse NotFound } getOrElse NotFound
@@ -167,34 +166,22 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound } getOrElse NotFound
}) })
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository => ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
params("id").toIntOpt.flatMap{ issueId => params("id").toIntOpt.flatMap{ issueId =>
val owner = repository.owner val owner = repository.owner
val name = repository.name val name = repository.name
getPullRequest(owner, name, issueId) map { case(issue, pullreq) => 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) checkConflict(owner, name, pullreq.branch, issueId)
} }
val hasMergePermission = hasWritePermission(owner, name, context.loginAccount) val hasProblem = hasConfrict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
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)
html.mergeguide( html.mergeguide(
mergeStatus, hasConfrict,
hasProblem,
issue, issue,
pullreq, pullreq,
statuses,
repository, repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get) getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get)
} }
@@ -216,75 +203,6 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound } 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) => post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId => params("id").toIntOpt.flatMap { issueId =>
val owner = repository.owner val owner = repository.owner
@@ -610,15 +528,4 @@ trait PullRequestsControllerBase extends ControllerBase {
repository, repository,
hasWritePermission(owner, repoName, context.loginAccount)) 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")
}
}
}
}
} }

View File

@@ -2,14 +2,14 @@ package gitbucket.core.controller
import gitbucket.core.settings.html import gitbucket.core.settings.html
import gitbucket.core.model.WebHook 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.service.WebHookService._
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.JGitUtil._ import gitbucket.core.util.JGitUtil._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ 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.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
@@ -18,29 +18,23 @@ import org.eclipse.jgit.lib.ObjectId
class RepositorySettingsController extends RepositorySettingsControllerBase class RepositorySettingsController extends RepositorySettingsControllerBase
with RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService with RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator with OwnerAuthenticator with UsersAuthenticator
trait RepositorySettingsControllerBase extends ControllerBase { trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService self: RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator => with OwnerAuthenticator with UsersAuthenticator =>
// for repository options // 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( val optionsForm = mapping(
"repositoryName" -> trim(label("Repository Name", text(required, maxlength(40), identifier, renameRepositoryName))), "repositoryName" -> trim(label("Repository Name", text(required, maxlength(40), identifier, renameRepositoryName))),
"description" -> trim(label("Description" , optional(text()))), "description" -> trim(label("Description" , optional(text()))),
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
"isPrivate" -> trim(label("Repository Type", boolean())) "isPrivate" -> trim(label("Repository Type", boolean()))
)(OptionsForm.apply) )(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 // for collaborator addition
case class CollaboratorForm(userName: String) case class CollaboratorForm(userName: String)
@@ -81,10 +75,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Save the repository options. * Save the repository options.
*/ */
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
val defaultBranch = if(repository.branchList.isEmpty) "master" else form.defaultBranch
saveRepositoryOptions( saveRepositoryOptions(
repository.owner, repository.owner,
repository.name, repository.name,
form.description, form.description,
defaultBranch,
repository.repository.parentUserName.map { _ => repository.repository.parentUserName.map { _ =>
repository.repository.isPrivate repository.repository.isPrivate
} getOrElse form.isPrivate } getOrElse form.isPrivate
@@ -102,61 +98,14 @@ trait RepositorySettingsControllerBase extends ControllerBase {
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName)) 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." flash += "info" -> "Repository settings has been updated."
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options") 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. * Display the Collaborators page.
*/ */

View File

@@ -14,11 +14,12 @@ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, WebHook} import gitbucket.core.model.{Account, CommitState, WebHook}
import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.WebHookService._ import gitbucket.core.service.WebHookService._
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers 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.apache.commons.io.FileUtils
import org.eclipse.jgit.api.{ArchiveCommand, Git} import org.eclipse.jgit.api.{ArchiveCommand, Git}
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat} import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
@@ -33,7 +34,7 @@ import org.scalatra._
class RepositoryViewerController extends RepositoryViewerControllerBase class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService with WebHookPullRequestService with WebHookPullRequestReviewCommentService
/** /**
* The repository viewer. * The repository viewer.
@@ -41,7 +42,7 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
trait RepositoryViewerControllerBase extends ControllerBase { trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService 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("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat) ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
@@ -110,7 +111,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
enableRefsLink = params("enableRefsLink").toBoolean, enableRefsLink = params("enableRefsLink").toBoolean,
enableLineBreaks = params("enableLineBreaks").toBoolean, enableLineBreaks = params("enableLineBreaks").toBoolean,
enableTaskList = params("enableTaskList").toBoolean, enableTaskList = params("enableTaskList").toBoolean,
enableAnchor = false,
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount) hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount)
) )
}) })
@@ -221,15 +221,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
get("/:owner/:repository/new/*")(collaboratorsOnly { repository => get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head) 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, html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")), None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
protectedBranch)
}) })
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository => get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head) 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 => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
@@ -237,8 +234,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
getPathObjectId(git, path, revCommit).map { objectId => getPathObjectId(git, path, revCommit).map { objectId =>
val paths = path.split("/") val paths = path.split("/")
html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last), html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
JGitUtil.getContentInfo(git, path, objectId), JGitUtil.getContentInfo(git, path, objectId))
protectedBranch)
} getOrElse NotFound } getOrElse NotFound
} }
}) })
@@ -307,7 +303,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
getPathObjectId(git, path, revCommit).flatMap { objectId => getPathObjectId(git, path, revCommit).flatMap { objectId =>
JGitUtil.getObjectLoaderFromId(git, objectId){ loader => JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
contentType = FileUtil.getMimeType(path) contentType = "application/octet-stream"
response.setContentLength(loader.getSize.toInt) response.setContentLength(loader.getSize.toInt)
loader.copyTo(response.outputStream) loader.copyTo(response.outputStream)
() ()
@@ -328,7 +324,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
if(raw){ if(raw){
// Download (This route is left for backword compatibility) // Download (This route is left for backword compatibility)
JGitUtil.getObjectLoaderFromId(git, objectId){ loader => JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
contentType = FileUtil.getMimeType(path) contentType = "application/octet-stream"
response.setContentLength(loader.getSize.toInt) response.setContentLength(loader.getSize.toInt)
loader.copyTo(response.outputStream) loader.copyTo(response.outputStream)
() ()
@@ -489,7 +485,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Displays branches. * Displays branches.
*/ */
get("/:owner/:repository/branches")(referrersOnly { repository => get("/:owner/:repository/branches")(referrersOnly { repository =>
val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet
val branches = JGitUtil.getBranches( val branches = JGitUtil.getBranches(
owner = repository.owner, owner = repository.owner,
name = repository.name, name = repository.name,
@@ -497,7 +492,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
origin = repository.repository.originUserName.isEmpty origin = repository.repository.originUserName.isEmpty
) )
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime)) .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 .reverse
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository) 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 val parentPath = if (path == ".") Nil else path.split("/").toList
// process README.md or README.markdown // process README.md or README.markdown
val readme = files.find { file => val readme = files.find { file =>
!file.isDirectory && readmeFiles.contains(file.name.toLowerCase) readmeFiles.contains(file.name.toLowerCase)
}.map { file => }.map { file =>
val path = (file.name :: parentPath.reverse).reverse val path = (file.name :: parentPath.reverse).reverse
path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId( path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
@@ -688,7 +683,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
headName, loginAccount.fullName, loginAccount.mailAddress, message) headName, loginAccount.fullName, loginAccount.mailAddress, message)
inserter.flush() inserter.flush()
inserter.close() inserter.release()
// update refs // update refs
val refUpdate = git.getRepository.updateRef(headName) val refUpdate = git.getRepository.updateRef(headName)

View File

@@ -5,7 +5,7 @@ import gitbucket.core.service._
import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits} import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits}
import ControlUtil._ import ControlUtil._
import Implicits._ import Implicits._
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
class SearchController extends SearchControllerBase class SearchController extends SearchControllerBase
with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator

View File

@@ -5,7 +5,7 @@ import gitbucket.core.service.{AccountService, SystemSettingsService}
import gitbucket.core.util.AdminAuthenticator import gitbucket.core.util.AdminAuthenticator
import gitbucket.core.ssh.SshServer import gitbucket.core.ssh.SshServer
import SystemSettingsService._ import SystemSettingsService._
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
class SystemSettingsController extends SystemSettingsControllerBase class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with AdminAuthenticator with AccountService with AdminAuthenticator

View File

@@ -7,7 +7,7 @@ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils

View File

@@ -7,7 +7,7 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ 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.eclipse.jgit.api.Git
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
@@ -38,9 +38,7 @@ trait WikiControllerBase extends ControllerBase {
get("/:owner/:repository/wiki")(referrersOnly { repository => get("/:owner/:repository/wiki")(referrersOnly { repository =>
getWikiPage(repository.owner, repository.name, "Home").map { page => getWikiPage(repository.owner, repository.name, "Home").map { page =>
html.page("Home", page, getWikiPageList(repository.owner, repository.name), html.page("Home", page, getWikiPageList(repository.owner, repository.name),
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
getWikiPage(repository.owner, repository.name, "_Sidebar"),
getWikiPage(repository.owner, repository.name, "_Footer"))
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit") } 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 => getWikiPage(repository.owner, repository.name, pageName).map { page =>
html.page(pageName, page, getWikiPageList(repository.owner, repository.name), html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
getWikiPage(repository.owner, repository.name, "_Sidebar"),
getWikiPage(repository.owner, repository.name, "_Footer"))
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit") } 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) updateLastActivityDate(repository.owner, repository.name)
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId) 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)}")
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} }
}) })
@@ -148,11 +140,7 @@ trait WikiControllerBase extends ControllerBase {
updateLastActivityDate(repository.owner, repository.name) updateLastActivityDate(repository.owner, repository.name)
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName) recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
if(notReservedPageName(form.pageName)) { redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} }
}) })
@@ -198,15 +186,13 @@ trait WikiControllerBase extends ControllerBase {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if(value.exists("\\/:*?\"<>|".contains(_))){ if(value.exists("\\/:*?\"<>|".contains(_))){
Some(s"${name} contains invalid character.") 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.") Some(s"${name} starts with invalid character.")
} else { } else {
None None
} }
} }
private def notReservedPageName(value: String) = ! (Array[String]("_Sidebar","_Footer") contains value)
private def conflictForNew: Constraint = new Constraint(){ private def conflictForNew: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = { override def validate(name: String, value: String, messages: Messages): Option[String] = {
targetWikiPage.map { _ => targetWikiPage.map { _ =>

View File

@@ -26,16 +26,12 @@ protected[model] trait TemplateComponent { self: Profile =>
trait LabelTemplate extends BasicTemplate { self: Table[_] => trait LabelTemplate extends BasicTemplate { self: Table[_] =>
val labelId = column[Int]("LABEL_ID") val labelId = column[Int]("LABEL_ID")
val labelName = column[String]("LABEL_NAME")
def byLabel(owner: String, repository: String, labelId: Int) = def byLabel(owner: String, repository: String, labelId: Int) =
byRepository(owner, repository) && (this.labelId === labelId.bind) byRepository(owner, repository) && (this.labelId === labelId.bind)
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
byRepository(userName, repositoryName) && (this.labelId === labelId) 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[_] => trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
@@ -58,9 +54,4 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.commitId === commitId) 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)
}
} }

View File

@@ -19,7 +19,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
val creator = column[String]("CREATOR") val creator = column[String]("CREATOR")
val registeredDate = column[java.util.Date]("REGISTERED_DATE") val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_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 def byPrimaryKey(id: Int) = commitStatusId === id.bind
} }
} }
@@ -38,20 +38,7 @@ case class CommitStatus(
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: 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) sealed abstract class CommitState(val name: String)

View File

@@ -7,7 +7,7 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate { class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate {
override val labelId = column[Int]("LABEL_ID", O AutoInc) 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") val color = column[String]("COLOR")
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply) def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)

View File

@@ -50,6 +50,5 @@ trait CoreProfile extends ProfileProvider with Profile
with WebHookComponent with WebHookComponent
with WebHookEventComponent with WebHookEventComponent
with PluginComponent with PluginComponent
with ProtectedBranchComponent
object Profile extends CoreProfile object Profile extends CoreProfile

View File

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

View File

@@ -67,16 +67,6 @@ trait Plugin {
*/ */
def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil 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. * This method is invoked in initialization of plugin system.
* Register plugin functionality to PluginRegistry. * Register plugin functionality to PluginRegistry.
@@ -97,9 +87,6 @@ trait Plugin {
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing => (repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>
registry.addRepositoryRouting(routing) registry.addRepositoryRouting(routing)
} }
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook =>
registry.addReceiveHook(receiveHook)
}
} }
/** /**

View File

@@ -6,7 +6,6 @@ import javax.servlet.ServletContext
import javax.servlet.http.{HttpServletRequest, HttpServletResponse} import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.controller.{Context, ControllerBase} import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
@@ -30,8 +29,6 @@ class PluginRegistry {
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer "md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer
) )
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting] private val repositoryRoutings = new ListBuffer[GitRepositoryRouting]
private val receiveHooks = new ListBuffer[ReceiveHook]
receiveHooks += new ProtectedBranchReceiveHook()
def addPlugin(pluginInfo: PluginInfo): Unit = { def addPlugin(pluginInfo: PluginInfo): Unit = {
plugins += pluginInfo 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( private case class GlobalAction(
method: String, method: String,
path: String, path: String,
@@ -178,7 +169,7 @@ object PluginRegistry {
)) ))
} catch { } catch {
case e: Throwable => { case e: Exception => {
logger.error(s"Error during plugin initialization", e) logger.error(s"Error during plugin initialization", e)
} }
} }

View File

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

View File

@@ -7,51 +7,46 @@ import gitbucket.core.model.{CommitState, CommitStatus, Account}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil._
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import org.joda.time.LocalDateTime
import gitbucket.core.model.Profile.dateColumnType
trait CommitStatusService { trait CommitStatusService {
/** insert or update */ /** insert or update */
def createCommitStatus(userName: String, repositoryName: String, sha: String, context: String, state: CommitState, 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 =
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 )
CommitStatuses
.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind )
.map(_.commitStatusId).firstOption match { .map(_.commitStatusId).firstOption match {
case Some(id: Int) => { case Some(id:Int) => {
CommitStatuses.filter(_.byPrimaryKey(id)).map { t => CommitStatuses.filter(_.byPrimaryKey(id)).map{
(t.state , t.targetUrl , t.updatedDate , t.creator, t.description) t => (t.state , t.targetUrl , t.updatedDate , t.creator, t.description)
}.update((state, targetUrl, now, creator.userName, description)) }.update( (state, targetUrl, now, creator.userName, description) )
id id
} }
case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus( case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus(
userName = userName, userName = userName,
repositoryName = repositoryName, repositoryName = repositoryName,
commitId = sha, commitId = sha,
context = context, context = context,
state = state, state = state,
targetUrl = targetUrl, targetUrl = targetUrl,
description = description, description = description,
creator = creator.userName, creator = creator.userName,
registeredDate = now, registeredDate = now,
updatedDate = now) updatedDate = now)
} }
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] = def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] =
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption 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] = 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] = def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[CommitStatus] =
byCommitStatues(userName, repositoryName, sha).list 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)] = 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) = 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)
} }

View File

@@ -12,9 +12,6 @@ trait LabelsService {
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] = def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption 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 = def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int =
Labels returning Labels.map(_.labelId) += Label( Labels returning Labels.map(_.labelId) += Label(
userName = owner, userName = owner,

View File

@@ -10,9 +10,10 @@ import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
import org.eclipse.jgit.errors.NoMergeBaseException 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 import org.eclipse.jgit.revwalk.RevWalk
trait MergeService { trait MergeService {
import MergeService._ import MergeService._
/** /**
@@ -51,30 +52,26 @@ trait MergeService {
/** /**
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused. * Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
*/ */
def tryMergeRemote(localUserName: String, localRepositoryName: String, localBranch: String, def checkConflict(userName: String, repositoryName: String, branch: String,
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String): Option[(ObjectId, ObjectId, ObjectId)] = { requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git => using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git =>
val remoteRefName = s"refs/heads/${remoteBranch}" val remoteRefName = s"refs/heads/${branch}"
val tmpRefName = s"refs/remote-temp/${remoteUserName}/${remoteRepositoryName}/${remoteBranch}" val tmpRefName = s"refs/merge-check/${userName}/${branch}"
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true) val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
try { try {
// fetch objects from origin repository branch // fetch objects from origin repository branch
git.fetch git.fetch
.setRemote(getRepositoryDir(remoteUserName, remoteRepositoryName).toURI.toString) .setRemote(getRepositoryDir(userName, repositoryName).toURI.toString)
.setRefSpecs(refSpec) .setRefSpecs(refSpec)
.call .call
// merge conflict check // merge conflict check
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) 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) val mergeTip = git.getRepository.resolve(tmpRefName)
try { try {
if(merger.merge(mergeBaseTip, mergeTip)){ !merger.merge(mergeBaseTip, mergeTip)
Some((merger.getResultTreeId, mergeBaseTip, mergeTip))
} else {
None
}
} catch { } catch {
case e: NoMergeBaseException => None case e: NoMergeBaseException => true
} }
} finally { } finally {
val refUpdate = git.getRepository.updateRef(refSpec.getDestination) 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 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){ case class MergeCacheInfo(git:Git, branch:String, issueId:Int){
val repository = git.getRepository val repository = git.getRepository
val mergedBranchName = s"refs/pull/${issueId}/merge" val mergedBranchName = s"refs/pull/${issueId}/merge"
@@ -139,17 +90,17 @@ object MergeService{
lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head") lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head")
def checkConflictCache(): Option[Boolean] = { def checkConflictCache(): Option[Boolean] = {
Option(repository.resolve(mergedBranchName)).flatMap{ merged => 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 // merged branch exists
Some(false) Some(false)
} else { }else{
None None
} }
}.orElse(Option(repository.resolve(conflictedBranchName)).flatMap{ conflicted => }.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 // conflict branch exists
Some(true) Some(true)
} else { }else{
None None
} }
}) })
@@ -169,12 +120,17 @@ object MergeService{
def updateBranch(treeId:ObjectId, message:String, branchName:String){ def updateBranch(treeId:ObjectId, message:String, branchName:String){
// creates merge commit // creates merge commit
val mergeCommitId = createMergeCommit(treeId, committer, message) 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){ if(!conflicted){
updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName) updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName)
git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call() git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call()
} else { }else{
updateBranch(mergeTipCommit.getTree().getId(), s"can't merge ${mergeTip.name} into ${mergeBaseTip.name}", conflictedBranchName) updateBranch(mergeTipCommit.getTree().getId(), s"can't merge ${mergeTip.name} into ${mergeBaseTip.name}", conflictedBranchName)
git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call() git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call()
} }
@@ -189,12 +145,28 @@ object MergeService{
// creates merge commit // creates merge commit
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message) val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
// update refs // 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 // return treeId
private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) = private def createMergeCommit(treeId:ObjectId, committer:PersonIdent, message:String) = {
Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip)) 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)) private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id))
} }
} }

View File

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

View File

@@ -1,6 +1,6 @@
package gitbucket.core.service 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.model.Profile._
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
import profile.simple._ import profile.simple._
@@ -145,29 +145,4 @@ object PullRequestService {
case class PullRequestCount(userName: String, count: Int) 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
}
} }

View File

@@ -79,8 +79,8 @@ trait RepositorySearchService { self: IssuesService =>
} }
} }
} }
treeWalk.close() treeWalk.release
revWalk.close() revWalk.release
list.toList list.toList
} }

View File

@@ -46,20 +46,18 @@ trait RepositoryService { self: AccountService =>
(Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository => (Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository =>
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName) Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val webHookEvents = WebHookEvents .filter(_.byRepository(oldUserName, oldRepositoryName)).list val webHookEvents = WebHookEvents .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val commitComments = CommitComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
val commitStatuses = CommitStatuses .filter(_.byRepository(oldUserName, oldRepositoryName)).list val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list
val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val protectedBranches = ProtectedBranches .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val protectedBranchContexts = ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list
Repositories.filter { t => Repositories.filter { t =>
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind) (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)) :_*) PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
CommitComments .insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) CommitComments.insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
CommitStatuses .insertAll(commitStatuses.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)) :_*)
// Update source repository of pull requests // Update source repository of pull requests
PullRequests.filter { t => PullRequests.filter { t =>
@@ -313,17 +309,11 @@ trait RepositoryService { self: AccountService =>
/** /**
* Save repository options. * Save repository options.
*/ */
def saveRepositoryOptions(userName: String, repositoryName: String, def saveRepositoryOptions(userName: String, repositoryName: String,
description: Option[String], isPrivate: Boolean)(implicit s: Session): Unit = description: Option[String], defaultBranch: String, isPrivate: Boolean)(implicit s: Session): Unit =
Repositories.filter(_.byRepository(userName, repositoryName)) Repositories.filter(_.byRepository(userName, repositoryName))
.map { r => (r.description.?, r.isPrivate, r.updatedDate) } .map { r => (r.description.?, r.defaultBranch, r.isPrivate, r.updatedDate) }
.update (description, isPrivate, currentDate) .update (description, defaultBranch, 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)
/** /**
* Add collaborator to the repository. * Add collaborator to the repository.

View File

@@ -93,7 +93,7 @@ trait WikiService {
def getWikiPageList(owner: String, repository: String): List[String] = { def getWikiPageList(owner: String, repository: String): List[String] = {
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
JGitUtil.getFileList(git, "master", ".") JGitUtil.getFileList(git, "master", ".")
.filter(_.name.endsWith(".md")).filterNot(_.name.startsWith("_")) .filter(_.name.endsWith(".md"))
.map(_.name.stripSuffix(".md")) .map(_.name.stripSuffix(".md"))
.sortBy(x => x) .sortBy(x => x)
} }

View File

@@ -18,10 +18,9 @@ import gitbucket.core.util.Directory
object AutoUpdate { 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( val versions = Seq(
new Version(3, 11),
new Version(3, 10), new Version(3, 10),
new Version(3, 9), new Version(3, 9),
new Version(3, 8), new Version(3, 8),
@@ -156,7 +155,7 @@ object AutoUpdate {
) )
/** /**
* The head version of GitBucket. * The head version of BitBucket.
*/ */
val headVersion = versions.head val headVersion = versions.head

View File

@@ -111,21 +111,13 @@ import scala.collection.JavaConverters._
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session) class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session)
extends PostReceiveHook with PreReceiveHook extends PostReceiveHook with PreReceiveHook
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService
with WebHookPullRequestService with ProtectedBranchService { with WebHookPullRequestService {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
private var existIds: Seq[String] = Nil private var existIds: Seq[String] = Nil
def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = { def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
try { 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 => using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
existIds = JGitUtil.getAllCommitIds(git) existIds = JGitUtil.getAllCommitIds(git)
} }
@@ -215,9 +207,6 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
newId = command.getNewId(), oldId = command.getOldId()) newId = command.getNewId(), oldId = command.getOldId())
} }
} }
// call post-commit hook
PluginRegistry().getReceiveHooks.foreach(_.postReceive(owner, repository, receivePack, command, pusher))
} }
} }
// update repository last modified time. // update repository last modified time.

View File

@@ -23,10 +23,10 @@ object GitCommand {
abstract class GitCommand() extends Command { abstract class GitCommand() extends Command {
private val logger = LoggerFactory.getLogger(classOf[GitCommand]) private val logger = LoggerFactory.getLogger(classOf[GitCommand])
@volatile protected var err: OutputStream = null protected var err: OutputStream = null
@volatile protected var in: InputStream = null protected var in: InputStream = null
@volatile protected var out: OutputStream = null protected var out: OutputStream = null
@volatile protected var callback: ExitCallback = null protected var callback: ExitCallback = null
protected def runTask(user: String)(implicit session: Session): Unit protected def runTask(user: String)(implicit session: Session): Unit

View File

@@ -1,6 +1,8 @@
package gitbucket.core.util package gitbucket.core.util
import org.eclipse.jgit.api.Git 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.util.control.Exception._
import scala.language.reflectiveCalls import scala.language.reflectiveCalls
@@ -29,6 +31,12 @@ object ControlUtil {
git2.getRepository.close() 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 { def ignore[T](f: => Unit): Unit = try {
f f
} catch { } catch {

View File

@@ -110,8 +110,6 @@ object JGitUtil {
newIsImage: Boolean, newIsImage: Boolean,
oldObjectId: Option[String], oldObjectId: Option[String],
newObjectId: Option[String], newObjectId: Option[String],
oldMode: String,
newMode: String,
tooLarge: Boolean tooLarge: Boolean
) )
@@ -517,8 +515,6 @@ object JGitUtil {
newIsImage = newIsImage, newIsImage = newIsImage,
oldObjectId = None, oldObjectId = None,
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name), newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
oldMode = treeWalk.getFileMode(0).toString,
newMode = treeWalk.getFileMode(0).toString,
tooLarge = false tooLarge = false
) )
} else { } else {
@@ -532,8 +528,6 @@ object JGitUtil {
newIsImage = newIsImage, newIsImage = newIsImage,
oldObjectId = None, oldObjectId = None,
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name), newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
oldMode = treeWalk.getFileMode(0).toString,
newMode = treeWalk.getFileMode(0).toString,
tooLarge = false tooLarge = false
) )
})) }))
@@ -568,8 +562,6 @@ object JGitUtil {
newIsImage = false, newIsImage = false,
oldObjectId = Option(diff.getOldId).map(_.name), oldObjectId = Option(diff.getOldId).map(_.name),
newObjectId = Option(diff.getNewId).map(_.name), newObjectId = Option(diff.getNewId).map(_.name),
oldMode = diff.getOldMode.toString,
newMode = diff.getNewMode.toString,
tooLarge = true tooLarge = true
) )
} else { } else {
@@ -586,8 +578,6 @@ object JGitUtil {
newIsImage = newIsImage, newIsImage = newIsImage,
oldObjectId = Option(diff.getOldId).map(_.name), oldObjectId = Option(diff.getOldId).map(_.name),
newObjectId = Option(diff.getNewId).map(_.name), newObjectId = Option(diff.getNewId).map(_.name),
oldMode = diff.getOldMode.toString,
newMode = diff.getNewMode.toString,
tooLarge = false tooLarge = false
) )
} else { } else {
@@ -601,8 +591,6 @@ object JGitUtil {
newIsImage = newIsImage, newIsImage = newIsImage,
oldObjectId = Option(diff.getOldId).map(_.name), oldObjectId = Option(diff.getOldId).map(_.name),
newObjectId = Option(diff.getNewId).map(_.name), newObjectId = Option(diff.getNewId).map(_.name),
oldMode = diff.getOldMode.toString,
newMode = diff.getNewMode.toString,
tooLarge = false tooLarge = false
) )
} }
@@ -641,7 +629,7 @@ object JGitUtil {
def initRepository(dir: java.io.File): Unit = def initRepository(dir: java.io.File): Unit =
using(new RepositoryBuilder().setGitDir(dir).setBare.build){ repository => using(new RepositoryBuilder().setGitDir(dir).setBare.build){ repository =>
repository.create(true) repository.create
setReceivePack(repository) setReceivePack(repository)
} }
@@ -700,7 +688,7 @@ object JGitUtil {
val newHeadId = inserter.insert(newCommit) val newHeadId = inserter.insert(newCommit)
inserter.flush() inserter.flush()
inserter.close() inserter.release()
val refUpdate = git.getRepository.updateRef(ref) val refUpdate = git.getRepository.updateRef(ref)
refUpdate.setNewObjectId(newHeadId) refUpdate.setNewObjectId(newHeadId)

View File

@@ -1,6 +1,6 @@
package gitbucket.core.util package gitbucket.core.util
import io.github.gitbucket.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
trait Validations { trait Validations {

View File

@@ -33,30 +33,28 @@ object Markdown {
enableTaskList: Boolean = false, enableTaskList: Boolean = false,
hasWritePermission: Boolean = false, hasWritePermission: Boolean = false,
pages: List[String] = Nil)(implicit context: Context): String = { 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 // escape task list
val source = if(enableTaskList) escapeTaskList(markdown) else markdown val source = if(enableTaskList){
escapeTaskList(s)
} else s
val options = new Options() val options = new Options()
options.setSanitize(true) options.setSanitize(true)
options.setBreaks(enableLineBreaks) 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) Marked.marked(source, options, renderer)
} }
/** /**
* Extends markedj Renderer for GitBucket * Extends markedj Renderer for GitBucket
*/ */
class GitBucketMarkedRenderer(options: Options, class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo,
repository: RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean,
enableWikiLink: Boolean,
enableRefsLink: Boolean,
enableAnchor: Boolean,
enableTaskList: Boolean,
hasWritePermission: Boolean,
pages: List[String]) pages: List[String])
(implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache {
@@ -64,14 +62,11 @@ object Markdown {
val id = generateAnchorName(text) val id = generateAnchorName(text)
val out = new StringBuilder() 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){ if(enableAnchor){
out.append(" class=\"markdown-head\">") out.append("<a class=\"markdown-anchor-link\" href=\"#" + id + "\"></a>")
out.append("<a class=\"markdown-anchor-link\" href=\"#" + id + "\"><span class=\"octicon octicon-link\"></span></a>")
out.append("<a class=\"markdown-anchor\" name=\"" + id + "\"></a>") out.append("<a class=\"markdown-anchor\" name=\"" + id + "\"></a>")
} else {
out.append(">")
} }
out.append(text) out.append(text)
@@ -88,27 +83,28 @@ object Markdown {
var listType: String = null var listType: String = null
if (ordered) { if (ordered) {
listType = "ol" listType = "ol"
} else { }
else {
listType = "ul" listType = "ul"
} }
if(body.contains("""class="task-list-item-checkbox"""")){ 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 { } else {
"<" + listType + ">\n" + body + "</" + listType + ">\n" return "<" + listType + ">\n" + body + "</" + listType + ">\n"
} }
} }
override def listitem(text: String): String = { override def listitem(text: String): String = {
if(text.contains("""class="task-list-item-checkbox" """)){ 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 { } else {
"<li>" + text + "</li>\n" return "<li>" + text + "</li>\n"
} }
} }
override def text(text: String): String = { override def text(text: String): String = {
// convert commit id and username to link. // 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. // convert task list to checkbox.
val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1

View File

@@ -89,7 +89,6 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
enableWikiLink: Boolean, enableWikiLink: Boolean,
enableRefsLink: Boolean, enableRefsLink: Boolean,
enableLineBreaks: Boolean, enableLineBreaks: Boolean,
enableAnchor: Boolean = true,
enableTaskList: Boolean = false, enableTaskList: Boolean = false,
hasWritePermission: Boolean = false, hasWritePermission: Boolean = false,
pages: List[String] = Nil)(implicit context: Context): Html = pages: List[String] = Nil)(implicit context: Context): Html =
@@ -98,7 +97,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
repository = repository, repository = repository,
enableWikiLink = enableWikiLink, enableWikiLink = enableWikiLink,
enableRefsLink = enableRefsLink, enableRefsLink = enableRefsLink,
enableAnchor = enableAnchor, enableAnchor = true,
enableLineBreaks = enableLineBreaks, enableLineBreaks = enableLineBreaks,
enableTaskList = enableTaskList, enableTaskList = enableTaskList,
hasWritePermission = hasWritePermission, hasWritePermission = hasWritePermission,
@@ -289,10 +288,10 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
} }
def commitStateIcon(state: CommitState) = Html(state match { 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.PENDING => ""
case CommitState.SUCCESS => """<i style="color:inherit;width:inherit;height:inherit" class="octicon octicon-check"></i>""" case CommitState.SUCCESS => "&#x2714;"
case CommitState.ERROR => """<i style="color:inherit;width:inherit;height:inherit" class="octicon octicon-x"></i>""" case CommitState.ERROR => "×"
case CommitState.FAILURE => """<i style="color:inherit;width:inherit;height:inherit" class="octicon octicon-x"></i>""" case CommitState.FAILURE => "×"
}) })
def commitStateText(state: CommitState, commitId:String) = state match { def commitStateText(state: CommitState, commitId:String) = state match {

View File

@@ -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> <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> </div>
} }
<span class="diffstat"><i class="octicon octicon-diff-renamed"></i></span> <span class="diffstat">
<span class="monospace">@diff.oldPath → @diff.newPath</span> <i class="octicon octicon-diff-renamed"></i>
</span> @diff.oldPath -> @diff.newPath
} }
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){ @if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
@if(newCommitId.isDefined){ @if(newCommitId.isDefined){
@@ -72,7 +73,7 @@
<i class="octicon octicon-diff-modified"></i> <i class="octicon octicon-diff-modified"></i>
} }
</span> </span>
<span class="monospace">@diff.newPath</span> @diff.newPath
} }
@if(diff.changeType == ChangeType.DELETE){ @if(diff.changeType == ChangeType.DELETE){
@if(oldCommitId.isDefined){ @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> <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> </div>
} }
<span class="diffstat"><i class="octicon octicon-diff-removed"></i></span> <span class="diffstat">
<span class="monospace">@diff.oldPath</span> <i class="octicon octicon-diff-removed"></i>
} </span> @diff.oldPath
@if(diff.oldMode != diff.newMode){
<span class="monospace">@diff.oldMode → @diff.newMode</span>
} }
</th> </th>
</tr> </tr>
<tr> <tr>
<td style="padding: 0;"> <td style="padding: 0;">
@if(diff.oldObjectId == diff.newObjectId){ @if(diff.oldObjectId == diff.newObjectId){
@if(diff.oldPath != diff.newPath){ <div class="diff-same">File renamed without changes</div>
<div class="diff-same">File renamed without changes</div>
} else {
<div class="diff-same">File mode changed</div>
}
} else { } else {
@if(diff.newContent != None || diff.oldContent != None){ @if(diff.newContent != None || diff.oldContent != None){
<div id="diffText-@i" class="diffText"></div> <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> <textarea id="oldText-@i" style="display: none;" data-file-name="@diff.newPath">@diff.oldContent.getOrElse("")</textarea>
} else { } else {
@if(diff.newIsImage || diff.oldIsImage){ @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){ @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> <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 { } else {

View File

@@ -1,8 +1,8 @@
@(page: Int, count: Int, limit: Int, width: Int, baseURL: String) @(page: Int, count: Int, limit: Int, width: Int, baseURL: String)
@defining(gitbucket.core.view.Pagination(page, count, limit, width)){ p => @defining(gitbucket.core.view.Pagination(page, count, limit, width)){ p =>
@if(p.count > p.limit){ @if(p.count > p.limit){
<div> <div class="pagination">
<ul class="pagination"> <ul>
@if(page == 1){ @if(page == 1){
<li class="disabled"><span>&#9664;</span></li> <li class="disabled"><span>&#9664;</span></li>
} else { } else {

View File

@@ -15,7 +15,7 @@
@import gitbucket.core._ @import gitbucket.core._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
<div class="tabbable"> <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 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> <li><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li>
</ul> </ul>

View File

@@ -48,7 +48,7 @@
} }
@if(userRepositories.size > max){ @if(userRepositories.size > max){
<li class="list-group-item show-more"> <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> </li>
} }
} }
@@ -62,19 +62,12 @@
@if(recentRepositories.isEmpty){ @if(recentRepositories.isEmpty){
<li class="list-group-item">No repositories</li> <li class="list-group-item">No repositories</li>
} else { } else {
@defining(20){ max => @recentRepositories.map { repository =>
@recentRepositories.zipWithIndex.map { case (repository, i) => <li class="list-group-item repo-link">
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false) @helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a> <a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</li> </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> </ul>
</div> </div>
@@ -84,7 +77,7 @@
} }
<script> <script>
$(function(){ $(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('ul.list-group').find('li.repo-link').show();
$(e.target).parents('li.show-more').remove(); $(e.target).parents('li.show-more').remove();
}); });

View File

@@ -30,12 +30,10 @@
</div> </div>
</div> </div>
} }
@issueOrPullRequest()={ @if(issue.isDefined && issue.get.isPullRequest)( "pull request" )else( "issue" ) }
@comments.map { @comments.map {
case comment: gitbucket.core.model.IssueComment => { case comment: gitbucket.core.model.IssueComment => {
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch" @if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
&& comment.action != "commit" && comment.action != "refer"){
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div> <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 panel-default issue-comment-box" id="comment-@comment.commentId">
<div class="panel-heading"> <div class="panel-heading">
@@ -44,7 +42,7 @@
@if(comment.action == "comment"){ @if(comment.action == "comment"){
commented commented
} else { } else {
referenced the @issueOrPullRequest() @if(pullreq.isEmpty){ referenced the issue } else { referenced the pull request }
} }
@helper.html.datetimeago(comment.registeredDate) @helper.html.datetimeago(comment.registeredDate)
</span> </span>
@@ -55,114 +53,79 @@
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a> <a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a>
</span> </span>
} }
</div> @if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
<div class="panel-body issue-content markdown-body" id="commentContent-@comment.commentId"> @defining(comment.content.substring(comment.content.length - 40)){ id =>
@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 =>
<span class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></span> <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>
</div> <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(
@if(comment.action == "refer"){ markdown = comment.content.substring(0, comment.content.length - 41),
<div class="discussion-item discussion-item-refer"> repository = repository,
<div class="discussion-item-header"> enableWikiLink = false,
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span> enableRefsLink = true,
@avatar(comment.commentedUserName, 16) enableLineBreaks = true,
@user(comment.commentedUserName, styleClass="username strong") enableTaskList = true,
referenced the @issueOrPullRequest() hasWritePermission = hasWritePermission
@helper.html.datetimeago(comment.registeredDate) )
</div> } else {
<div style="discussion-item-content"> @if(comment.action == "refer"){
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) => @defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
<strong>@issueLink(repository, issueId.toInt): @rest.mkString(":")</strong> <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>
</div> </div>
} }
@if(comment.action == "merge"){ @if(comment.action == "merge"){
<div class="discussion-item discussion-item-merge"> <div class="small" style="margin-top: 10px; margin-bottom: 10px;">
<div class="discussion-item-header"> <span class="label label-info">Merged</span>
<span class="discussion-item-icon"><i class="octicon octicon-git-merge"></i></span> @avatar(comment.commentedUserName, 20)
@avatar(comment.commentedUserName, 16) @user(comment.commentedUserName, styleClass="username strong") merged commit <code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into
@user(comment.commentedUserName, styleClass="username strong") @if(pullreq.get.requestUserName == repository.owner){
merged commit <span class="label label-info monospace">@pullreq.map(_.branch)</span> from <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span>
<code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into } else {
@if(pullreq.get.requestUserName == repository.owner){ <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>
<code>@pullreq.map(_.branch)</code> from <code>@pullreq.map(_.requestBranch)</code> }
} else { @helper.html.datetimeago(comment.registeredDate)
<code>@pullreq.map(_.userName):@pullreq.map(_.branch)</code> from <code>@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</code>
}
@helper.html.datetimeago(comment.registeredDate)
</div>
</div> </div>
} }
@if(comment.action == "close" || comment.action == "close_comment"){ @if(comment.action == "close" || comment.action == "close_comment"){
<div class="discussion-item discussion-item-close"> <div class="issue-comment-action">
<div class="discussion-item-header"> <i class="octicon octicon-circle-slash danger"></i>
<span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span> @avatar(comment.commentedUserName, 20)
@avatar(comment.commentedUserName, 16) @if(issue.isDefined && issue.get.isPullRequest){
@user(comment.commentedUserName, styleClass="username strong") @user(comment.commentedUserName, styleClass="username strong") closed the pull request @helper.html.datetimeago(comment.registeredDate)
close @issueOrPullRequest() } else {
@helper.html.datetimeago(comment.registeredDate) @user(comment.commentedUserName, styleClass="username strong") closed the issue @helper.html.datetimeago(comment.registeredDate)
</div> }
</div> </div>
} }
@if(comment.action == "reopen" || comment.action == "reopen_comment"){ @if(comment.action == "reopen" || comment.action == "reopen_comment"){
<div class="discussion-item discussion-item-reopen"> <div class="issue-comment-action issue-reopened">
<div class="discussion-item-header"> <i class="octicon octicon-primitive-dot"></i>
<span class="discussion-item-icon"><i class="octicon octicon-primitive-dot"></i></span> @avatar(comment.commentedUserName, 20)
@avatar(comment.commentedUserName, 16) @user(comment.commentedUserName, styleClass="username strong") reopened the issue @helper.html.datetimeago(comment.registeredDate)
@user(comment.commentedUserName, styleClass="username strong")
reopened the @issueOrPullRequest()
@helper.html.datetimeago(comment.registeredDate)
</div>
</div> </div>
} }
@if(comment.action == "delete_branch"){ @if(comment.action == "delete_branch"){
<div class="discussion-item discussion-item-delete_branch"> <div class="issue-comment-action">
<div class="discussion-item-header"> <span class="label">Deleted</span>
<span class="discussion-item-icon"><i class="octicon octicon-git-branch"></i></span> @avatar(comment.commentedUserName, 20)
@avatar(comment.commentedUserName, 16) @user(comment.commentedUserName, styleClass="username strong") deleted the <span class="label label-info monospace">@pullreq.map(_.requestBranch)</span> branch @helper.html.datetimeago(comment.registeredDate)
@user(comment.commentedUserName, styleClass="username strong")
deleted the <code>@pullreq.map(_.requestBranch)</code> branch
@helper.html.datetimeago(comment.registeredDate)
</div>
</div> </div>
} }
} }

View File

@@ -28,7 +28,7 @@
<td style="padding: 20px; background-color: #eee; text-align: center;"> <td style="padding: 20px; background-color: #eee; text-align: center;">
No labels to show. No labels to show.
@if(hasWritePermission){ @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> </td>
</tr> </tr>

View File

@@ -78,7 +78,8 @@
<div class="pull-right" style="margin-top: 6px;"> <div class="pull-right" style="margin-top: 6px;">
<div class="btn-group" style="margin-right: 8px;"> <div class="btn-group" style="margin-right: 8px;">
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#"> <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> </a>
<ul class="dropdown-menu pull-right"> <ul class="dropdown-menu pull-right">
<li><a href="@path/new">New repository</a></li> <li><a href="@path/new">New repository</a></li>
@@ -86,8 +87,8 @@
</ul> </ul>
</div> </div>
<div class="btn-group"> <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"> <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)
@avatar(loginAccount.get.userName, 16)<span class="caret" style="color: black; vertical-align: middle;"></span> <span class="caret" style="color: black; vertical-align: middle;"></span>
</a> </a>
<ul class="dropdown-menu pull-right"> <ul class="dropdown-menu pull-right">
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li> <li><a href="@url(loginAccount.get.userName)">Your profile</a></li>

View File

@@ -15,7 +15,7 @@
<i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i> <i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i>
@if(expand){ @label} @if(expand){ @label}
@if(expand && count > 0){ @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> </a>
</li> </li>
@@ -105,7 +105,7 @@
} }
} }
</div> </div>
<div class="pull-left" style="width: @if(expand){770px} else {895px};"> <div style="margin-right: @if(expand){180px} else {55px};">
@if(expand){ @if(expand){
@repository.repository.description.map { description => @repository.repository.description.map { description =>
<p class="description">@detectAndRenderLinks(description)</p> <p class="description">@detectAndRenderLinks(description)</p>
@@ -116,11 +116,7 @@
<td style="width: 33%; text-align: center;"> <td style="width: 33%; text-align: center;">
<a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link"> <a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link">
<i class="octicon octicon-history"></i> <i class="octicon octicon-history"></i>
@if(repository.commitCount > 10000){ <strong>@repository.commitCount</strong> commits
<strong>10000+</strong> commits
} else {
<strong>@repository.commitCount</strong> commits
}
</a> </a>
</td> </td>
<td style="width: 33%; text-align: center;"> <td style="width: 33%; text-align: center;">

View File

@@ -20,7 +20,7 @@
case comment: gitbucket.core.model.IssueComment => Some(comment) case comment: gitbucket.core.model.IssueComment => Some(comment)
case other => None case other => None
}.exists(_.action == "merge")){ merged => }.exists(_.action == "merge")){ merged =>
@if(!issue.closed){ @if(hasWritePermission && !issue.closed){
<div class="check-conflict" style="display: none;"> <div class="check-conflict" style="display: none;">
<div class="box issue-comment-box" style="background-color: #fbeed5"> <div class="box issue-comment-box" style="background-color: #fbeed5">
<div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;"> <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> <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>
</div> </div>
}
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
} }
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
}
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
@issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository) @issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
@@ -55,12 +55,10 @@ $(function(){
$('#merge-pull-request').show(); $('#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){ @if(hasWritePermission){
$('.check-conflict').show();
$.get('@url(repository)/pull/@issue.issueId/mergeguide', function(data){ $('.check-conflict').html(data); });
$('.delete-branch').click(function(e){ $('.delete-branch').click(function(e){
var branchName = $(e.target).data('name'); var branchName = $(e.target).data('name');
return confirm('Are you sure you want to remove the ' + branchName + ' branch?'); return confirm('Are you sure you want to remove the ' + branchName + ' branch?');

View File

@@ -1,139 +1,120 @@
@(status: gitbucket.core.service.PullRequestService.MergeStatus, @(hasConflict: Boolean,
hasProblem: Boolean,
issue: gitbucket.core.model.Issue, issue: gitbucket.core.model.Issue,
pullreq: gitbucket.core.model.PullRequest, pullreq: gitbucket.core.model.PullRequest,
statuses: List[model.CommitStatus],
originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo, originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.service.SystemSettingsService @import gitbucket.core.service.SystemSettingsService
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import model.CommitState @import model.CommitState
<div class="box issue-comment-box" style="background-color: @if(status.hasProblem){ #fbeed5 }else{ #d8f5cd };"> <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(status.hasProblem){ #c09853 }else{ #95c97e };padding:0"> <div class="box-content issue-content" style="border: 1px solid @if(hasProblem){ #c09853 }else{ #95c97e }; padding: 10px;">
<div id="merge-pull-request"> <div id="merge-pull-request">
@if(!status.statuses.isEmpty){ @if(!statuses.isEmpty){
<div class="build-statuses"> <div class="build-statuses">
@defining(status.commitStateSummary){ case (summaryState, summary) => @if(statuses.size==1){
<div class="build-status-item-header"> @defining(statuses.head){ status =>
<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) =>
<div class="build-status-item"> <div class="build-status-item">
<div class="pull-right"> @status.targetUrl.map{ url => <a class="pull-right" href="@url">Details</a> }
@if(required){ <span class="label">Required</span> }
@status.targetUrl.map{ url => <a href="@url">Details</a> }
</div>
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span> <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> } @status.description.map{ desc => <span class="muted">— @desc</span> }
</div> </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 { } else {
<span class="strong">This branch has no conflicts with the base branch.</span> @defining(statuses.groupBy(_.state)){ stateMap =>
<div class="small"> @defining(CommitState.combine(stateMap.keySet)){ state =>
Only those with write access to this repository can merge pull requests. <div class="build-status-item">
</div> <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>
} }
<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 cant 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>
<div id="confirm-merge-form" style="display: none;"> <div id="confirm-merge-form" style="display: none;">
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge"> <form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge">
@@ -153,8 +134,8 @@
<script> <script>
$(function(){ $(function(){
$('.show-command-line').click(function(){ $('#show-command-line').click(function(){
$('#command-line').toggle(); $('#command-line').show();
return false; return false;
}); });
function setToggleAllChecksLabel(){ function setToggleAllChecksLabel(){

View File

@@ -8,8 +8,7 @@
dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]], dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo], diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
hasWritePermission: Boolean, hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
flash: Map[String, String])(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import gitbucket.core.model._ @import gitbucket.core.model._
@@ -74,12 +73,6 @@
</ul> </ul>
<div class="tab-content fill-width pull-left"> <div class="tab-content fill-width pull-left">
<div class="tab-pane active" id="conversation"> <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) @pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div> </div>
<div class="tab-pane" id="commits"> <div class="tab-pane" id="commits">

View File

@@ -7,7 +7,7 @@
isBlame: Boolean)(implicit context: gitbucket.core.controller.Context) isBlame: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @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){ @html.menu("code", repository){
<div class="head"> <div class="head">
<div class="pull-right hide-if-blame"><div class="btn-group"> <div class="pull-right hide-if-blame"><div class="btn-group">

View File

@@ -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, hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@@ -13,7 +13,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@branchInfo.map { case (branch, prs, isProtected) => @branchInfo.map { case (branch, prs) =>
<tr><td style="padding: 12px;"> <tr><td style="padding: 12px;">
<div class="branch-action"> <div class="branch-action">
@branch.mergeInfo.map{ info => @branch.mergeInfo.map{ info =>
@@ -48,18 +48,13 @@
@if(prs.map(!_._2.closed).getOrElse(false)){ @if(prs.map(!_._2.closed).getOrElse(false)){
<a class="disabled" data-toggle="tooltip" title="You cant delete this branch because it has an open pull request"><i class="octicon octicon-trashcan"></i></a> <a class="disabled" data-toggle="tooltip" title="You cant delete this branch because it has an open pull request"><i class="octicon octicon-trashcan"></i></a>
} else { } else {
@if(isProtected){ <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 class="disabled" data-toggle="tooltip" title="You cant 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>
}
} }
</span> </span>
} }
} }
</div> </div>
<div class="branch-details"> <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> <a href="@url(repository)/tree/@encodeRefName(branch.name)" class="branch-name">@branch.name</a>
<span class="branch-meta"> <span class="branch-meta">
<span>Updated @helper.html.datetimeago(branch.commitTime, false) <span>Updated @helper.html.datetimeago(branch.commitTime, false)

View File

@@ -2,15 +2,11 @@
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
pathList: List[String], pathList: List[String],
fileName: Option[String], fileName: Option[String],
content: gitbucket.core.util.JGitUtil.ContentInfo, content: gitbucket.core.util.JGitUtil.ContentInfo)(implicit context: gitbucket.core.controller.Context)
protectedBranch: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @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.main(if(fileName.isEmpty) "New File" else s"Editing ${fileName.get} at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", 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"> <form method="POST" action="@url(repository)/@if(fileName.isEmpty){create}else{update}" validate="true">
<span class="error" id="error-newFileName"></span> <span class="error" id="error-newFileName"></span>
<div class="head"> <div class="head">
@@ -86,9 +82,6 @@ $(function(){
@if(fileName.isDefined){ @if(fileName.isDefined){
editor.getSession().setMode("ace/mode/@editorType(fileName.get)"); editor.getSession().setMode("ace/mode/@editorType(fileName.get)");
} }
@if(protectedBranch){
editor.setReadOnly(true);
}
editor.on('change', function(){ editor.on('change', function(){
updateCommitButtonStatus(); updateCommitButtonStatus();
@@ -129,17 +122,16 @@ $(function(){
$('#editor').hide(); $('#editor').hide();
$('#preview').show(); $('#preview').show();
$('#btn-code').removeClass('active'); $('#btn-code').removeClass('active');
$('#btn-preview').addClass('active'); $('#btn-preview').appendClass('active');
@if(fileName.map(isRenderable _).getOrElse(false)) { @if(fileName.map(isRenderable _).getOrElse(false)) {
// update preview // update preview
$('#preview').html('<img src="@assets/common/images/indicator.gif"> Previewing...'); $('#preview').html('<img src="@assets/common/images/indicator.gif"> Previewing...');
$.post('@url(repository)/_preview', { $.post('@url(repository)/_preview', {
content : editor.getValue(), content : editor.getValue(),
enableWikiLink : false, enableWikiLink : false,
enableRefsLink : false, enableRefsLink : false,
enableLineBreaks : false, enableTaskList : false
enableTaskList : false
}, function(data){ }, function(data){
$('#preview').empty().append( $('#preview').empty().append(
$('<div class="markdown-body" style="padding-left: 20px; padding-right: 20px;">').html(data)); $('<div class="markdown-body" style="padding-left: 20px; padding-right: 20px;">').html(data));

View File

@@ -11,16 +11,7 @@
error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context) error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main( @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
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.menu("code", repository, Some(branch), pathList.isEmpty, groupNames.isEmpty, info, error){ @html.menu("code", repository, Some(branch), pathList.isEmpty, groupNames.isEmpty, info, error){
<div class="head"> <div class="head">
<div class="pull-right"><div class="btn-group"> <div class="pull-right"><div class="btn-group">
@@ -106,25 +97,25 @@
} }
</td> </td>
<td class="content-name"> <td class="content-name">
@if(file.isDirectory){ @if(file.isDirectory){
@if(file.linkUrl.isDefined){ @if(file.linkUrl.isDefined){
<a href="@file.linkUrl"> <a href="@file.linkUrl">
<span class="simplified-path">@file.name.split("/").toList.init match { <span class="simplified-path">@file.name.split("/").toList.init match {
case Nil => {} case Nil => {}
case list => {@list.mkString("", "/", "/")} case list => {@list.mkString("", "/", "/")}
}</span>@file.name.split("/").toList.last }</span>@file.name.split("/").toList.last
</a> </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>
}
} else { } 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>
<td class="mute"> <td class="mute">
<a href="@url(repository)/commit/@file.commitId" class="commit-message shorten-text" title="@file.message">@link(file.message, repository)</a> <a href="@url(repository)/commit/@file.commitId" class="commit-message shorten-text" title="@file.message">@link(file.message, repository)</a>

View File

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

View File

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

View File

@@ -11,11 +11,6 @@
<li@if(active=="collaborators"){ class="active"}> <li@if(active=="collaborators"){ class="active"}>
<a href="@url(repository)/settings/collaborators">Collaborators</a> <a href="@url(repository)/settings/collaborators">Collaborators</a>
</li> </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"}> <li@if(active=="hooks"){ class="active"}>
<a href="@url(repository)/settings/hooks">Service Hooks</a> <a href="@url(repository)/settings/hooks">Service Hooks</a>
</li> </li>

View File

@@ -18,6 +18,22 @@
<label for="description" class="strong">Description:</label> <label for="description" class="strong">Description:</label>
<input type="text" name="description" id="description" class="form-control" value="@repository.repository.description"/> <input type="text" name="description" id="description" class="form-control" value="@repository.repository.description"/>
</fieldset> </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"> <fieldset class="margin">
<label class="radio"> <label class="radio">
<input type="radio" name="isPrivate" value="false" <input type="radio" name="isPrivate" value="false"

View File

@@ -10,7 +10,7 @@
@html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
@helper.html.information(info) @helper.html.information(info)
@html.menu("wiki", repository){ @html.menu("wiki", repository){
<ul class="nav nav-tabs fill-width"> <ul class="nav nav-tabs fill-width pull-left">
<li> <li>
<h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1> <h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1>
</li> </li>

View File

@@ -5,7 +5,7 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"${if(pageName.isEmpty) "New Page" else pageName} - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"${if(pageName.isEmpty) "New Page" else pageName} - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("wiki", repository){ @html.menu("wiki", repository){
<ul class="nav nav-tabs fill-width"> <ul class="nav nav-tabs fill-width pull-left">
<li> <li>
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1> <h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
</li> </li>

View File

@@ -5,7 +5,7 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"History - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"History - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("wiki", repository){ @html.menu("wiki", repository){
<ul class="nav nav-tabs fill-width"> <ul class="nav nav-tabs fill-width pull-left">
<li> <li>
<h1 class="wiki-title"> <h1 class="wiki-title">
@if(pageName.isEmpty){ @if(pageName.isEmpty){

View File

@@ -2,15 +2,13 @@
page: gitbucket.core.service.WikiService.WikiPageInfo, page: gitbucket.core.service.WikiService.WikiPageInfo,
pages: List[String], pages: List[String],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean, hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
sidebar: Option[gitbucket.core.service.WikiService.WikiPageInfo],
footer: Option[gitbucket.core.service.WikiService.WikiPageInfo])(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import gitbucket.core.service.WikiService._ @import gitbucket.core.service.WikiService._
@html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("wiki", repository){ @html.menu("wiki", repository){
<ul class="nav nav-tabs fill-width"> <ul class="nav nav-tabs fill-width pull-left">
<li> <li>
<h1 class="wiki-title">@pageName</h1> <h1 class="wiki-title">@pageName</h1>
<div class="small"> <div class="small">
@@ -30,13 +28,9 @@
@defining(15){ max => @defining(15){ max =>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading strong"> <div class="panel-heading strong">
<a id="show-pages-index" href="javascript:void(0);"> Pages <span class="badge">@pages.length</span>
<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>
</div> </div>
<ul id="pages-index" class="list-group list-group-flush"> <ul class="list-group list-group-flush">
@pages.zipWithIndex.map { case (page, i) => @pages.zipWithIndex.map { case (page, i) =>
<li class="list-group-item page-link" style="border: none; @if(i > max - 1){display:none;}"> <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> <a href="@url(repository)/wiki/@urlEncode(page)" class="strong">@page</a>
@@ -50,20 +44,6 @@
</ul> </ul>
</div> </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"> <div class="small">
<strong>Clone this wiki locally</strong> <strong>Clone this wiki locally</strong>
</div> </div>
@@ -89,20 +69,6 @@
pages = pages pages = pages
) )
</div> </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> </div>
} }
} }
@@ -113,24 +79,6 @@ $(function(){
$(e.target).parents('div.show-more').remove(); $(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){ @if(settings.ssh && loginAccount.isDefined){
$('#repository-url-http').click(function(){ $('#repository-url-http').click(function(){
$('#repository-url').val('@httpUrl(repository)'); $('#repository-url').val('@httpUrl(repository)');

View File

@@ -5,7 +5,7 @@
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){ @html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("wiki", repository){ @html.menu("wiki", repository){
<ul class="nav nav-tabs fill-width"> <ul class="nav nav-tabs fill-width pull-left">
<li> <li>
<h1 class="wiki-title"><span class="muted">Pages</span></h1> <h1 class="wiki-title"><span class="muted">Pages</span></h1>
</li> </li>

View File

@@ -146,24 +146,6 @@ hr {
margin-bottom: 4px; 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 */ /* Global Header */
/* ======================================================================== */ /* ======================================================================== */
@@ -582,7 +564,7 @@ ul.sidemenu li {
} }
ul.sidemenu span.badge { ul.sidemenu span.badge {
padding-right: 4px;
} }
ul.sidemenu a:hover i.menu-icon, ul.sidemenu i.menu-icon-active { 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; margin-bottom: -3px;
} }
a#show-pages-index {
color: #333;
text-decoration: none;
}
/* /*
ul.nav-stacked.side-menu li span.header { ul.nav-stacked.side-menu li span.header {
border-top-right-radius: 3px; border-top-right-radius: 3px;
@@ -847,9 +824,6 @@ table.table-file-list .file-icon {
table.table-file-list .content-name { table.table-file-list .content-name {
max-width: 180px; max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
table.table-file-list .commit-message { table.table-file-list .commit-message {
@@ -1336,74 +1310,6 @@ li.task-list-item input.task-list-item-checkbox {
vertical-align: middle; 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 */ /* Pull Request */
/****************************************************************************/ /****************************************************************************/
@@ -1425,59 +1331,6 @@ a.absent {
color: #c00; 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 */ /* Commit */
/****************************************************************************/ /****************************************************************************/
@@ -1498,43 +1351,13 @@ div.author-info div.committer {
margin: -10px -10px 10px -10px; margin: -10px -10px 10px -10px;
} }
.build-statuses .build-status-item{ .build-statuses .build-status-item{
padding: 10px 15px 10px 64px; padding: 10px 15px 10px 12px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
.build-statuses-list .build-status-item{ .build-statuses-list .build-status-item{
background-color: #fafafa; 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 */ /* Diff */
/****************************************************************************/ /****************************************************************************/
@@ -1838,39 +1661,28 @@ div.markdown-body h1 {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
font-size: 2.5em; font-size: 2.5em;
font-weight: bold; font-weight: bold;
line-height: 1.7;
} }
div.markdown-body h2 { div.markdown-body h2 {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
font-size: 2em; font-size: 2em;
font-weight: bold;
line-height: 1.7;
} }
div.markdown-body h3 { div.markdown-body h3 {
font-size: 1.5em; font-size: 1.5em;
font-weight: bold;
line-height: 1.7;
} }
div.markdown-body h4 { div.markdown-body h4 {
font-size: 1.2em; font-size: 1.2em;
font-weight: bold;
line-height: 1.7;
} }
div.markdown-body h5 { div.markdown-body h5 {
font-size: 1em; font-size: 1em;
font-weight: bold;
line-height: 1.7;
} }
div.markdown-body h6 { div.markdown-body h6 {
color:#777; color:#777;
font-size: 1em; font-size: 1em;
font-weight: bold;
line-height: 1.7;
} }
div.markdown-body li { div.markdown-body li {
@@ -1882,10 +1694,6 @@ div.markdown-body p {
line-height: 1.5; line-height: 1.5;
} }
div.markdown-body img {
max-width: 100%;
}
div.markdown-body pre { div.markdown-body pre {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px; font-size: 12px;
@@ -2040,19 +1848,43 @@ div.markdown-body table colgroup + tbody tr:first-child td:last-child {
.markdown-head { .markdown-head {
position: relative; position: relative;
line-height: 1.7;
} }
a.markdown-anchor-link { a.markdown-anchor-link {
margin-left: -16px; position: absolute;
margin-right: 2px; left: -18px;
line-height: 1; display: none;
color: #999; 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 { h2 a.markdown-anchor-link {
visibility: hidden; top: 20px;
vertical-align: middle; }
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;
} }
/****************************************************************************/ /****************************************************************************/

View File

@@ -19,11 +19,14 @@ $(function(){
}); });
// anchor icon for markdown // anchor icon for markdown
$('.markdown-head').on('mouseenter', function(e){ $('.markdown-head').mouseenter(function(e){
$(this).find('span.octicon').css('visibility', 'visible'); $(e.target).children('a.markdown-anchor-link').show();
}); });
$('.markdown-head').on('mouseleave', function(e){ $('.markdown-head').mouseleave(function(e){
$(this).find('span.octicon').css('visibility', 'hidden'); $(e.target).children('a.markdown-anchor-link').hide();
});
$('a.markdown-anchor-link').mouseleave(function(e){
$(e.target).hide();
}); });
// syntax highlighting by google-code-prettify // syntax highlighting by google-code-prettify
@@ -603,7 +606,7 @@ var imageDiff ={
.css({marginTop:size.height-5}); .css({marginTop:size.height-5});
var bar = $('<hr class="diff-silde-bar">').css({top:size.height+size.paddingTop}); var bar = $('<hr class="diff-silde-bar">').css({top:size.height+size.paddingTop});
var div = $('<div class="diff-image-stack">') 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); .append(diffOld, diffNew, bar, handle);
return { return {
neo:diffNew, neo:diffNew,

View File

@@ -209,15 +209,6 @@ class JsonFormatSpec extends Specification {
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/$sha1/status" "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( val apiIssue = ApiIssue(
number = 1347, number = 1347,
title = "Found a bug", 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 beFormatted(json2Arg:String) = new Matcher[String] {
def apply[S <: String](e: Expectable[S]) = { def apply[S <: String](e: Expectable[S]) = {
@@ -420,9 +401,6 @@ class JsonFormatSpec extends Specification {
"apiCombinedCommitStatus" in { "apiCombinedCommitStatus" in {
JsonFormat(apiCombinedCommitStatus) must beFormatted(apiCombinedCommitStatusJson) JsonFormat(apiCombinedCommitStatus) must beFormatted(apiCombinedCommitStatusJson)
} }
"apiLabel" in {
JsonFormat(apiLabel) must beFormatted(apiLabelJson)
}
"apiIssue" in { "apiIssue" in {
JsonFormat(apiIssue) must beFormatted(apiIssueJson) JsonFormat(apiIssue) must beFormatted(apiIssueJson)
JsonFormat(apiIssuePR) must beFormatted(apiIssuePRJson) JsonFormat(apiIssuePR) must beFormatted(apiIssuePRJson)
@@ -433,8 +411,5 @@ class JsonFormatSpec extends Specification {
"apiPullRequestReviewComment" in { "apiPullRequestReviewComment" in {
JsonFormat(apiPullRequestReviewComment) must beFormatted(apiPullRequestReviewCommentJson) JsonFormat(apiPullRequestReviewComment) must beFormatted(apiPullRequestReviewCommentJson)
} }
"apiBranchProtection" in {
JsonFormat(apiBranchProtection) must beFormatted(apiBranchProtectionJson)
}
} }
} }

View File

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

View File

@@ -45,73 +45,73 @@ class MergeServiceSpec extends Specification {
conflicted mustEqual false conflicted mustEqual false
} }
"checkConflict true if not conflicted, and create cache" in { "checkConflict true if not conflicted, and create cache" in {
val repo2Dir = initRepository("user1","repo2") val repo1Dir = initRepository("user1","repo1")
using(Git.open(repo2Dir)){ git => using(Git.open(repo1Dir)){ git =>
createConfrict(git) createConfrict(git)
} }
service.checkConflictCache("user1", "repo2", branch, issueId) mustEqual None service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
val conflicted = service.checkConflict("user1", "repo2", branch, issueId) val conflicted = service.checkConflict("user1", "repo1", branch, issueId)
conflicted mustEqual true conflicted mustEqual true
service.checkConflictCache("user1", "repo2", branch, issueId) mustEqual Some(true) service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(true)
} }
} }
"checkConflictCache" should { "checkConflictCache" should {
"merged cache invalid if origin branch moved" in { "merged cache invalid if origin branch moved" in {
val repo3Dir = initRepository("user1","repo3") val repo1Dir = initRepository("user1","repo1")
service.checkConflict("user1", "repo3", branch, issueId) mustEqual false service.checkConflict("user1", "repo1", branch, issueId) mustEqual false
service.checkConflictCache("user1", "repo3", branch, issueId) mustEqual Some(false) service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(false)
using(Git.open(repo3Dir)){ git => using(Git.open(repo1Dir)){ git =>
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" ) 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 { "merged cache invalid if request branch moved" in {
val repo4Dir = initRepository("user1","repo4") val repo1Dir = initRepository("user1","repo1")
service.checkConflict("user1", "repo4", branch, issueId) mustEqual false service.checkConflict("user1", "repo1", branch, issueId) mustEqual false
service.checkConflictCache("user1", "repo4", branch, issueId) mustEqual Some(false) service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(false)
using(Git.open(repo4Dir)){ git => using(Git.open(repo1Dir)){ git =>
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" ) 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 { "merged cache invalid if origin branch moved" in {
val repo5Dir = initRepository("user1","repo5") val repo1Dir = initRepository("user1","repo1")
service.checkConflict("user1", "repo5", branch, issueId) mustEqual false service.checkConflict("user1", "repo1", branch, issueId) mustEqual false
service.checkConflictCache("user1", "repo5", branch, issueId) mustEqual Some(false) service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(false)
using(Git.open(repo5Dir)){ git => using(Git.open(repo1Dir)){ git =>
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" ) 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 { "conflicted cache invalid if request branch moved" in {
val repo6Dir = initRepository("user1","repo6") val repo1Dir = initRepository("user1","repo1")
using(Git.open(repo6Dir)){ git => using(Git.open(repo1Dir)){ git =>
createConfrict(git) createConfrict(git)
} }
service.checkConflict("user1", "repo6", branch, issueId) mustEqual true service.checkConflict("user1", "repo1", branch, issueId) mustEqual true
service.checkConflictCache("user1", "repo6", branch, issueId) mustEqual Some(true) service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(true)
using(Git.open(repo6Dir)){ git => using(Git.open(repo1Dir)){ git =>
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" ) 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 { "conflicted cache invalid if origin branch moved" in {
val repo7Dir = initRepository("user1","repo7") val repo1Dir = initRepository("user1","repo1")
using(Git.open(repo7Dir)){ git => using(Git.open(repo1Dir)){ git =>
createConfrict(git) createConfrict(git)
} }
service.checkConflict("user1", "repo7", branch, issueId) mustEqual true service.checkConflict("user1", "repo1", branch, issueId) mustEqual true
service.checkConflictCache("user1", "repo7", branch, issueId) mustEqual Some(true) service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(true)
using(Git.open(repo7Dir)){ git => using(Git.open(repo1Dir)){ git =>
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge4" ) 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 { "mergePullRequest" should {
"can merge" in { "can merge" in {
val repo8Dir = initRepository("user1","repo8") val repo1Dir = initRepository("user1","repo1")
using(Git.open(repo8Dir)){ git => using(Git.open(repo1Dir)){ git =>
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge2" ) createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge2" )
val committer = new PersonIdent("dummy2", "dummy2@example.com") val committer = new PersonIdent("dummy2", "dummy2@example.com")
getFile(git, branch, "test.txt").content.get mustEqual "hoge" getFile(git, branch, "test.txt").content.get mustEqual "hoge"

View File

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

View File

@@ -1,16 +1,18 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model._ import gitbucket.core.model._
import gitbucket.core.model.Profile._
import org.specs2.mutable.Specification import org.specs2.mutable.Specification
class RepositoryServiceSpec extends Specification with ServiceSpecBase with RepositoryService with AccountService{ class RepositoryServiceSpec extends Specification with ServiceSpecBase with RepositoryService with AccountService{
"RepositoryService" should { "RepositoryService" should {
"renameRepository can rename CommitState, ProtectedBranches" in { withTestDB { implicit session => "renameRepository can rename CommitState" in { withTestDB { implicit session =>
val tester = generateNewAccount("tester") val tester = generateNewAccount("tester")
createRepository("repo","root",None,false) createRepository("repo","root",None,false)
val service = new CommitStatusService with ProtectedBranchService {} val commitStatusService = new CommitStatusService{}
val id = service.createCommitStatus( val id = commitStatusService.createCommitStatus(
userName = "root", userName = "root",
repositoryName = "repo", repositoryName = "repo",
sha = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7", sha = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7",
@@ -20,20 +22,14 @@ class RepositoryServiceSpec extends Specification with ServiceSpecBase with Repo
description = Some("description"), description = Some("description"),
creator = tester, creator = tester,
now = new java.util.Date) now = new java.util.Date)
service.enableBranchProtection("root", "repo", "branch", true, Seq("must1", "must2")) val org = commitStatusService.getCommitStatus("root","repo", id).get
var orgPbi = service.getProtectedBranchInfo("root", "repo", "branch")
val org = service.getCommitStatus("root","repo", id).get
renameRepository("root","repo","tester","repo2") renameRepository("root","repo","tester","repo2")
val neo = commitStatusService.getCommitStatus("tester","repo2", org.commitId, org.context).get
val neo = service.getCommitStatus("tester","repo2", org.commitId, org.context).get
neo must_== neo must_==
org.copy( org.copy(
commitStatusId=neo.commitStatusId, commitStatusId=neo.commitStatusId,
repositoryName="repo2", repositoryName="repo2",
userName="tester") 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