From 8677146a8ded862b4555188dfd77d88d8270df63 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Wed, 26 Feb 2014 12:09:14 +0100 Subject: [PATCH 01/76] Show the correct name of the readme file (instead of showing always README.md). --- src/main/scala/app/RepositoryViewerController.scala | 2 +- src/main/twirl/repo/files.scala.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 272613682..fcc81dfe6 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -276,7 +276,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { val readme = files.find { file => readmeFiles.contains(file.name.toLowerCase) }.map { file => - StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get) + file -> StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get) } repo.html.files(revision, repository, diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 94f6dac3d..823298ec7 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -3,7 +3,7 @@ pathList: List[String], latestCommit: util.JGitUtil.CommitInfo, files: List[util.JGitUtil.FileInfo], - readme: Option[String])(implicit context: app.Context) + readme: Option[(util.JGitUtil.FileInfo, String)])(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @@ -77,9 +77,9 @@ - @readme.map { content => + @readme.map { case(file, content) =>
-
README.md
+
@file.name
@markdown(content, repository, false, false)
} From 9078aa6d085b6a268a7f6343a5c37536b054b38f Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Wed, 26 Feb 2014 13:53:50 +0100 Subject: [PATCH 02/76] Added asciidoctorj dependency. --- project/build.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project/build.scala b/project/build.scala index ce6d4d9f9..a87a457c6 100644 --- a/project/build.scala +++ b/project/build.scala @@ -15,6 +15,7 @@ object MyBuild extends Build { "gitbucket", file("."), settings = Defaults.defaultSettings ++ ScalatraPlugin.scalatraWithJRebel ++ Seq( + sourcesInBase := false, organization := Organization, name := Name, version := Version, @@ -42,7 +43,8 @@ object MyBuild extends Build { "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime", "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided", "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"), - "junit" % "junit" % "4.11" % "test" + "junit" % "junit" % "4.11" % "test", + "org.asciidoctor" % "asciidoctor-java-integration" % "0.1.4" ), EclipseKeys.withSource := true, javacOptions in compile ++= Seq("-target", "6", "-source", "6"), From 97b1a0090ddd1ab6f92a1286b118957e403a6ea2 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Wed, 26 Feb 2014 15:14:39 +0100 Subject: [PATCH 03/76] Initial support for rendering asciidoc files. --- .../app/RepositoryViewerController.scala | 2 +- src/main/scala/view/Asciidoc.scala | 30 +++++++++++++++++++ src/main/scala/view/helpers.scala | 25 ++++++++++++++++ src/main/twirl/repo/files.scala.html | 2 +- 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/view/Asciidoc.scala diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index fcc81dfe6..7337c2f25 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -251,7 +251,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { } - private val readmeFiles = Seq("readme.md", "readme.markdown") + private val readmeFiles = view.helpers.renderableSuffixes.map(suffix => s"readme${suffix}") /** * Provides HTML of the file list. diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala new file mode 100644 index 000000000..523f14651 --- /dev/null +++ b/src/main/scala/view/Asciidoc.scala @@ -0,0 +1,30 @@ +package view + +import util.StringUtil +import util.ControlUtil._ +import util.Directory._ +import org.parboiled.common.StringUtils +import org.pegdown._ +import org.pegdown.ast._ +import org.pegdown.LinkRenderer.Rendering +import java.text.Normalizer +import java.util.Locale +import scala.collection.JavaConverters._ +import service.{ RequestCache, WikiService } +import org.asciidoctor.{ Asciidoctor, OptionsBuilder, SafeMode } + +object Asciidoc { + + private[this] lazy val asciidoctor = Asciidoctor.Factory.create() + + /** + * Converts Markdown of Wiki pages to HTML. + */ + def toHtml(asciidoc: String, repository: service.RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = { + val options = OptionsBuilder.options() + options.safe(SafeMode.SECURE) + asciidoctor.render(asciidoc, options) + } +} + diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index 8dc2adf43..a06f97f4a 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -27,6 +27,16 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache def plural(count: Int, singular: String, plural: String = ""): String = if(count == 1) singular else if(plural.isEmpty) singular + "s" else plural + private[this] val renderersBySuffix: Seq[(String, (String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = + Seq( + ".md" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".markdown" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".adoc" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".asciidoc" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, repository, enableWikiLink, enableRefsLink)(context)) + ) + + def renderableSuffixes: Seq[String] = renderersBySuffix.map(_._1) + /** * Converts Markdown of Wiki pages to HTML. */ @@ -34,6 +44,21 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink)) + def renderMarkup(fileName: String, fileContent: String, + repository: service.RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = { + + val fileNameLower = fileName.toLowerCase + renderersBySuffix.find { case (suffix, _) => fileNameLower.endsWith(suffix) } match { + case Some((_, handler)) => handler(fileContent, repository, enableWikiLink, enableRefsLink, context) + case None => Html("UNSUPPORTED MARKUP TYPE") + } + } + + def asciidoc(value: String, repository: service.RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = + Html(Asciidoc.toHtml(value, repository, enableWikiLink, enableRefsLink)) + /** * Returns <img> which displays the avatar icon for the given user name. * This method looks up Gravatar if avatar icon has not been configured in user settings. diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 823298ec7..814e498f7 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -80,7 +80,7 @@ @readme.map { case(file, content) =>
@file.name
-
@markdown(content, repository, false, false)
+
@renderMarkup(file.name, content, repository, false, false)
} } From cbf615d699bdc9facdea642d95e0358bd4a1caa8 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Wed, 26 Feb 2014 16:13:39 +0100 Subject: [PATCH 04/76] Support plain text readme files (with .txt or no extension). --- src/main/scala/app/RepositoryViewerController.scala | 2 +- src/main/scala/view/helpers.scala | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 7337c2f25..5c796353f 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -251,7 +251,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { } - private val readmeFiles = view.helpers.renderableSuffixes.map(suffix => s"readme${suffix}") + private val readmeFiles = view.helpers.renderableSuffixes.map(suffix => s"readme${suffix}") ++ Seq("readme.txt", "readme") /** * Provides HTML of the file list. diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index a06f97f4a..b60b4b3d5 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -51,7 +51,11 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache val fileNameLower = fileName.toLowerCase renderersBySuffix.find { case (suffix, _) => fileNameLower.endsWith(suffix) } match { case Some((_, handler)) => handler(fileContent, repository, enableWikiLink, enableRefsLink, context) - case None => Html("UNSUPPORTED MARKUP TYPE") + case None => Html( + s"${ + fileContent.split("(\\r\\n)|\\n").map(xml.Utility.escape(_)).mkString("
") + }
" + ) } } From 4600b5a3bfa5d38dfbfbc461279ab1f7b0987087 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 28 Feb 2014 09:36:04 +0100 Subject: [PATCH 05/76] Enabled rendering of page document title. --- src/main/scala/view/Asciidoc.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala index 523f14651..978f49f13 100644 --- a/src/main/scala/view/Asciidoc.scala +++ b/src/main/scala/view/Asciidoc.scala @@ -11,7 +11,7 @@ import java.text.Normalizer import java.util.Locale import scala.collection.JavaConverters._ import service.{ RequestCache, WikiService } -import org.asciidoctor.{ Asciidoctor, OptionsBuilder, SafeMode } +import org.asciidoctor.{ Asciidoctor, Attributes, AttributesBuilder, OptionsBuilder, SafeMode } object Asciidoc { @@ -24,6 +24,9 @@ object Asciidoc { enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = { val options = OptionsBuilder.options() options.safe(SafeMode.SECURE) + val attributes = AttributesBuilder.attributes() + attributes.showTitle(true) + options.attributes(attributes.get()) asciidoctor.render(asciidoc, options) } } From 3db3bf1b74d2e64d89da6521bdce47031aae65ed Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 4 Mar 2014 11:38:56 +0100 Subject: [PATCH 06/76] Rewrite relative links to reflect the base url of the repo. --- project/build.scala | 3 +- src/main/scala/view/Asciidoc.scala | 52 ++++++++++++++++++++-------- src/main/scala/view/helpers.scala | 18 +++++----- src/main/twirl/repo/files.scala.html | 2 +- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/project/build.scala b/project/build.scala index a87a457c6..bcf125e95 100644 --- a/project/build.scala +++ b/project/build.scala @@ -44,7 +44,8 @@ object MyBuild extends Build { "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided", "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"), "junit" % "junit" % "4.11" % "test", - "org.asciidoctor" % "asciidoctor-java-integration" % "0.1.4" + "org.asciidoctor" % "asciidoctor-java-integration" % "0.1.4", + "net.sourceforge.htmlcleaner" % "htmlcleaner" % "2.7" ), EclipseKeys.withSource := true, javacOptions in compile ++= Seq("-target", "6", "-source", "6"), diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala index 978f49f13..8d9d37601 100644 --- a/src/main/scala/view/Asciidoc.scala +++ b/src/main/scala/view/Asciidoc.scala @@ -1,17 +1,14 @@ package view -import util.StringUtil -import util.ControlUtil._ -import util.Directory._ -import org.parboiled.common.StringUtils -import org.pegdown._ -import org.pegdown.ast._ -import org.pegdown.LinkRenderer.Rendering -import java.text.Normalizer -import java.util.Locale -import scala.collection.JavaConverters._ -import service.{ RequestCache, WikiService } -import org.asciidoctor.{ Asciidoctor, Attributes, AttributesBuilder, OptionsBuilder, SafeMode } +import org.asciidoctor.Asciidoctor +import org.asciidoctor.AttributesBuilder +import org.asciidoctor.OptionsBuilder +import org.asciidoctor.SafeMode +import org.htmlcleaner.HtmlCleaner +import org.htmlcleaner.HtmlNode +import org.htmlcleaner.SimpleHtmlSerializer +import org.htmlcleaner.TagNode +import org.htmlcleaner.TagNodeVisitor object Asciidoc { @@ -20,14 +17,41 @@ object Asciidoc { /** * Converts Markdown of Wiki pages to HTML. */ - def toHtml(asciidoc: String, repository: service.RepositoryService.RepositoryInfo, + def toHtml(asciidoc: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = { + val options = OptionsBuilder.options() options.safe(SafeMode.SECURE) val attributes = AttributesBuilder.attributes() attributes.showTitle(true) options.attributes(attributes.get()) - asciidoctor.render(asciidoc, options) + val rendered = asciidoctor.render(asciidoc, options) + + // this is always relative to the base dir of the repo, as we currently only render README files. + val relativeUrlPrefix = s"${helpers.url(repository)}/blob/${branch}/" + prefixRelativeUrls(rendered, relativeUrlPrefix) } + + def prefixRelativeUrls(html: String, urlPrefix: String): String = { + val cleaner = new HtmlCleaner() + val node = cleaner.clean(html) + node.traverse(new TagNodeVisitor() { + override def visit(tagNode: TagNode, htmlNode: HtmlNode): Boolean = { + htmlNode match { + case tag: TagNode if tag.getName == "a" => + Option(tag.getAttributeByName("href")) foreach { href => + if (!href.startsWith("/") && !href.startsWith("http://") && !href.startsWith("https://")) { + tag.addAttribute("href", s"${urlPrefix}${href}") + } + } + case _ => + } + // continue traversal + true + } + }) + new SimpleHtmlSerializer(cleaner.getProperties()).getAsString(node) + } + } diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index b60b4b3d5..4c7ad5b31 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -27,12 +27,12 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache def plural(count: Int, singular: String, plural: String = ""): String = if(count == 1) singular else if(plural.isEmpty) singular + "s" else plural - private[this] val renderersBySuffix: Seq[(String, (String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = + private[this] val renderersBySuffix: Seq[(String, (String, String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = Seq( - ".md" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".markdown" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".adoc" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".asciidoc" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, repository, enableWikiLink, enableRefsLink)(context)) + ".md" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".markdown" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".adoc" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)), + ".asciidoc" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)) ) def renderableSuffixes: Seq[String] = renderersBySuffix.map(_._1) @@ -44,13 +44,13 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink)) - def renderMarkup(fileName: String, fileContent: String, + def renderMarkup(fileName: String, fileContent: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = { val fileNameLower = fileName.toLowerCase renderersBySuffix.find { case (suffix, _) => fileNameLower.endsWith(suffix) } match { - case Some((_, handler)) => handler(fileContent, repository, enableWikiLink, enableRefsLink, context) + case Some((_, handler)) => handler(fileContent, branch, repository, enableWikiLink, enableRefsLink, context) case None => Html( s"${ fileContent.split("(\\r\\n)|\\n").map(xml.Utility.escape(_)).mkString("
") @@ -59,9 +59,9 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache } } - def asciidoc(value: String, repository: service.RepositoryService.RepositoryInfo, + def asciidoc(value: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = - Html(Asciidoc.toHtml(value, repository, enableWikiLink, enableRefsLink)) + Html(Asciidoc.toHtml(value, branch, repository, enableWikiLink, enableRefsLink)) /** * Returns <img> which displays the avatar icon for the given user name. diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 470021fce..5362b1448 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -80,7 +80,7 @@ @readme.map { case(file, content) =>
@file.name
-
@renderMarkup(file.name, content, repository, false, false)
+
@renderMarkup(file.name, content, branch, repository, false, false)
} } From 9e1352c8b155390ac8a5e0ab048e8a2c32c8914b Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 4 Mar 2014 16:19:00 +0100 Subject: [PATCH 07/76] Enabled rendering of renderable files in blob view. --- src/main/twirl/repo/blob.scala.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/twirl/repo/blob.scala.html b/src/main/twirl/repo/blob.scala.html index 25a9910b1..182a334d2 100644 --- a/src/main/twirl/repo/blob.scala.html +++ b/src/main/twirl/repo/blob.scala.html @@ -37,7 +37,13 @@ @if(content.viewType == "text"){ -
@content.content.get
+ @defining(pathList.reverse.head) { file => + @if(renderableSuffixes.find(suffix => file.toLowerCase.endsWith(suffix))) { + @renderMarkup(file, content.content.get, branch, repository, false, false) + } else { +
@content.content.get
+ } + } } @if(content.viewType == "image"){ From 4276c8f23ee08f4c173dba8111f90265c6ad34dc Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 4 Mar 2014 16:42:41 +0100 Subject: [PATCH 08/76] Support relative links in asciidoc files. --- .../app/RepositoryViewerController.scala | 23 +++++++++++-------- src/main/scala/view/Asciidoc.scala | 9 +++++--- src/main/scala/view/helpers.scala | 20 ++++++++-------- src/main/twirl/repo/blob.scala.html | 2 +- src/main/twirl/repo/files.scala.html | 8 +++---- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 5c796353f..6893e63b1 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -268,16 +268,19 @@ trait RepositoryViewerControllerBase extends ControllerBase { using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head) // get specified commit - JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) => - defining(JGitUtil.getRevCommitFromId(git, objectId)){ revCommit => - // get files - val files = JGitUtil.getFileList(git, revision, path) - // process README.md or README.markdown - val readme = files.find { file => - readmeFiles.contains(file.name.toLowerCase) - }.map { file => - file -> StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get) - } + JGitUtil.getDefaultBranch(git, repository, revstr).map { + case (objectId, revision) => + defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit => + // get files + val files = JGitUtil.getFileList(git, revision, path) + val parentPath = if (path == ".") Nil else path.split("/").toList + // process README.md or README.markdown + val readme = files.find { file => + readmeFiles.contains(file.name.toLowerCase) + }.map { file => + val path = (file.name :: parentPath.reverse).reverse + path -> StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get) + } repo.html.files(revision, repository, if(path == ".") Nil else path.split("/").toList, // current path diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala index 8d9d37601..88c80fb1d 100644 --- a/src/main/scala/view/Asciidoc.scala +++ b/src/main/scala/view/Asciidoc.scala @@ -17,7 +17,7 @@ object Asciidoc { /** * Converts Markdown of Wiki pages to HTML. */ - def toHtml(asciidoc: String, branch: String, repository: service.RepositoryService.RepositoryInfo, + def toHtml(filePath: List[String], asciidoc: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = { val options = OptionsBuilder.options() @@ -27,8 +27,11 @@ object Asciidoc { options.attributes(attributes.get()) val rendered = asciidoctor.render(asciidoc, options) - // this is always relative to the base dir of the repo, as we currently only render README files. - val relativeUrlPrefix = s"${helpers.url(repository)}/blob/${branch}/" + val path = filePath.reverse.tail.reverse match { + case Nil => "" + case p => p.mkString("", "/", "/") + } + val relativeUrlPrefix = s"${helpers.url(repository)}/blob/${branch}/${path}" prefixRelativeUrls(rendered, relativeUrlPrefix) } diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index 4c7ad5b31..feb5f21fb 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -27,12 +27,12 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache def plural(count: Int, singular: String, plural: String = ""): String = if(count == 1) singular else if(plural.isEmpty) singular + "s" else plural - private[this] val renderersBySuffix: Seq[(String, (String, String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = + private[this] val renderersBySuffix: Seq[(String, (List[String], String, String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = Seq( - ".md" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".markdown" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".adoc" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)), - ".asciidoc" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)) + ".md" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".markdown" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".adoc" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)), + ".asciidoc" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)) ) def renderableSuffixes: Seq[String] = renderersBySuffix.map(_._1) @@ -44,13 +44,13 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink)) - def renderMarkup(fileName: String, fileContent: String, branch: String, + def renderMarkup(filePath: List[String], fileContent: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = { - val fileNameLower = fileName.toLowerCase + val fileNameLower = filePath.reverse.head.toLowerCase renderersBySuffix.find { case (suffix, _) => fileNameLower.endsWith(suffix) } match { - case Some((_, handler)) => handler(fileContent, branch, repository, enableWikiLink, enableRefsLink, context) + case Some((_, handler)) => handler(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) case None => Html( s"${ fileContent.split("(\\r\\n)|\\n").map(xml.Utility.escape(_)).mkString("
") @@ -59,9 +59,9 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache } } - def asciidoc(value: String, branch: String, repository: service.RepositoryService.RepositoryInfo, + def asciidoc(filePath: List[String], value: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = - Html(Asciidoc.toHtml(value, branch, repository, enableWikiLink, enableRefsLink)) + Html(Asciidoc.toHtml(filePath, value, branch, repository, enableWikiLink, enableRefsLink)) /** * Returns <img> which displays the avatar icon for the given user name. diff --git a/src/main/twirl/repo/blob.scala.html b/src/main/twirl/repo/blob.scala.html index 182a334d2..acb193a14 100644 --- a/src/main/twirl/repo/blob.scala.html +++ b/src/main/twirl/repo/blob.scala.html @@ -39,7 +39,7 @@ @if(content.viewType == "text"){ @defining(pathList.reverse.head) { file => @if(renderableSuffixes.find(suffix => file.toLowerCase.endsWith(suffix))) { - @renderMarkup(file, content.content.get, branch, repository, false, false) + @renderMarkup(pathList, content.content.get, branch, repository, false, false) } else {
@content.content.get
} diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 5362b1448..c7e97e51d 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -3,7 +3,7 @@ pathList: List[String], latestCommit: util.JGitUtil.CommitInfo, files: List[util.JGitUtil.FileInfo], - readme: Option[(util.JGitUtil.FileInfo, String)])(implicit context: app.Context) + readme: Option[(List[String], String)])(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @@ -77,10 +77,10 @@ - @readme.map { case(file, content) => + @readme.map { case(filePath, content) =>
-
@file.name
-
@renderMarkup(file.name, content, branch, repository, false, false)
+
@filePath.reverse.head
+
@renderMarkup(filePath, content, branch, repository, false, false)
} } From af397ba150f2267ee59513afbf9aa80745c4b28a Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Wed, 5 Mar 2014 09:12:03 +0100 Subject: [PATCH 09/76] Fix page-relative links, e.g. in TOC. --- src/main/scala/view/Asciidoc.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala index 88c80fb1d..9404f7abf 100644 --- a/src/main/scala/view/Asciidoc.scala +++ b/src/main/scala/view/Asciidoc.scala @@ -35,6 +35,8 @@ object Asciidoc { prefixRelativeUrls(rendered, relativeUrlPrefix) } + private[this] val exceptionPrefixes = Seq("#", "/", "http://", "https://") + def prefixRelativeUrls(html: String, urlPrefix: String): String = { val cleaner = new HtmlCleaner() val node = cleaner.clean(html) @@ -43,7 +45,8 @@ object Asciidoc { htmlNode match { case tag: TagNode if tag.getName == "a" => Option(tag.getAttributeByName("href")) foreach { href => - if (!href.startsWith("/") && !href.startsWith("http://") && !href.startsWith("https://")) { + if (exceptionPrefixes.forall(p => !href.startsWith(p))) { + // if (!href.startsWith("/") && !href.startsWith("http://") && !href.startsWith("https://")) { tag.addAttribute("href", s"${urlPrefix}${href}") } } From 10a40bfcaf460d372f743e9226a38131994205e8 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Wed, 5 Mar 2014 09:28:13 +0100 Subject: [PATCH 10/76] Removed commented out code. --- src/main/scala/view/Asciidoc.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala index 9404f7abf..5c131c491 100644 --- a/src/main/scala/view/Asciidoc.scala +++ b/src/main/scala/view/Asciidoc.scala @@ -46,7 +46,6 @@ object Asciidoc { case tag: TagNode if tag.getName == "a" => Option(tag.getAttributeByName("href")) foreach { href => if (exceptionPrefixes.forall(p => !href.startsWith(p))) { - // if (!href.startsWith("/") && !href.startsWith("http://") && !href.startsWith("https://")) { tag.addAttribute("href", s"${urlPrefix}${href}") } } From 3bff6a194906632135145c01be9961cb61c18af7 Mon Sep 17 00:00:00 2001 From: kaakaa Date: Sat, 8 Mar 2014 23:43:23 +0900 Subject: [PATCH 11/76] Implement atom feeds --- src/main/scala/app/ControllerBase.scala | 3 ++ src/main/scala/app/IndexController.scala | 5 +++ src/main/scala/view/helpers.scala | 5 +++ src/main/twirl/feed.scala.xml | 30 ++++++++++++++++++ src/main/twirl/helper/activities.scala.html | 3 ++ src/main/webapp/assets/common/images/feed.png | Bin 0 -> 691 bytes 6 files changed, 46 insertions(+) create mode 100644 src/main/twirl/feed.scala.xml create mode 100644 src/main/webapp/assets/common/images/feed.png diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index d7d7171a2..b4c39ed04 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -143,6 +143,9 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: lazy val currentPath = request.getRequestURI.substring(request.getContextPath.length) + lazy val baseURL = request.getRequestURL.substring(0, request.getRequestURL.length - request.getRequestURI.length) + path + lazy val host = request.getServerName + /** * Get object from cache. * diff --git a/src/main/scala/app/IndexController.scala b/src/main/scala/app/IndexController.scala index 426ec631a..3bc662034 100644 --- a/src/main/scala/app/IndexController.scala +++ b/src/main/scala/app/IndexController.scala @@ -46,6 +46,11 @@ trait IndexControllerBase extends ControllerBase { redirect("/") } + get("/activities.atom"){ + contentType = "application/atom+xml; type=feed" + xml.feed(getRecentActivities()) + } + /** * Set account information into HttpSession and redirect. */ diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index 8dc2adf43..7b9f9492f 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -15,6 +15,11 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache */ def datetime(date: Date): String = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + /** + * Format java.util.Date to "yyyy-MM-dd'T'hh:mm:ss'Z'". + */ + def datetimeRFC3339(date: Date): String = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'").format(date).replaceAll("(\\d\\d)(\\d\\d)$","$1:$2") + /** * Format java.util.Date to "yyyy-MM-dd". */ diff --git a/src/main/twirl/feed.scala.xml b/src/main/twirl/feed.scala.xml new file mode 100644 index 000000000..f870ca3eb --- /dev/null +++ b/src/main/twirl/feed.scala.xml @@ -0,0 +1,30 @@ +@(activities: List[model.Activity])(implicit context: app.Context) +@import context._ +@import view.helpers._ + + @header(activities) + @activities.map { activity => @item(activity) } + + +@header(activities: List[model.Activity]) = {tag:@context.host,2013:gitbucket + Gitbucket's activities + + + Gitbucket + @context.baseURL + + @datetimeRFC3339(activities.map(_.activityDate).max)} + +@item(activity: model.Activity) = { + + tag:@context.host,@date(activity.activityDate):activity:@activity.activityId + @datetimeRFC3339(activity.activityDate) + @datetimeRFC3339(activity.activityDate) + + @activity.activityType + + @activity.activityUserName + @url(activity.activityUserName) + + @activityMessage(activity.message) + } diff --git a/src/main/twirl/helper/activities.scala.html b/src/main/twirl/helper/activities.scala.html index 6e7f324c2..e1458688f 100644 --- a/src/main/twirl/helper/activities.scala.html +++ b/src/main/twirl/helper/activities.scala.html @@ -5,6 +5,9 @@ @if(activities.isEmpty){ No activity } else { +
+ activities +
@activities.map { activity =>
@(activity.activityType match { diff --git a/src/main/webapp/assets/common/images/feed.png b/src/main/webapp/assets/common/images/feed.png new file mode 100644 index 0000000000000000000000000000000000000000..315c4f4fa62cb720326ba3f54259666ba3999e42 GIT binary patch literal 691 zcmV;k0!;mhP)bpQb1=l6TxbDZwj&S={?7%qx-u`rsG(Zp`-rh=e^=%((1yvsuf5d=&62Zj)Y zH&JviNS_F4_Hj|T(1j4$p-!}kixP9&dB4uv^MveG?dGf%sUCoc2!IFxD6wHRA2^dX zXRVk!-qSfk(jcaUKn#RP48(whfPlJUpApdrA!TQi_4D+fVoM;3I0gZ8{=Xv~Po;geVA+Em9@0Wq2 zr>OTZEGR05L=gf1T;ucCxq6Q6EgJiH@@-lVaAlQyw`jIF^c=&IVnj|95hHbE_cnt| zTzZQ?F4Ne@(bH(~&3nM%m)I@ID{@jJ2qZPjr)jhpe9hViOwH5k&|T#EmmL3(vHeUQ zq^!t^Al6JD;=mHq^Bg?J-8-zG2Od7gZbknG;K9czYjPqG*xjPo0k(c4%lPXTpw(qq z@aGMnxtFS(np+2kC} z7P02O874ZkJH$v#nCUVx$({yDN`IX@o2wyvTD#e`qN`_w5<}$3F+_ Date: Sun, 30 Mar 2014 20:52:39 +0900 Subject: [PATCH 12/76] Skip issue id extraction if the repository has no issues --- .../scala/servlet/GitRepositoryServlet.scala | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala index 60a5224dc..d02cb2f5e 100644 --- a/src/main/scala/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/servlet/GitRepositoryServlet.scala @@ -16,6 +16,7 @@ import service._ import WebHookService._ import org.eclipse.jgit.api.Git import util.JGitUtil.CommitInfo +import service.IssuesService.IssueSearchCondition /** * Provides Git repository via HTTP. @@ -110,19 +111,28 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: } } + // Retrieve all issue count in the repository + val issueCount = + countIssue(IssueSearchCondition(state = "open"), Map.empty, false, owner -> repository) + + countIssue(IssueSearchCondition(state = "closed"), Map.empty, false, owner -> repository) + // Extract new commit and apply issue comment val newCommits = if(commits.size > 1000){ val existIds = getAllCommitIds(owner, repository) commits.flatMap { commit => if(!existIds.contains(commit.id)){ - createIssueComment(commit) + if(issueCount > 0) { + createIssueComment(commit) + } Some(commit) } else None } } else { commits.flatMap { commit => if(!existsCommitId(owner, repository, commit.id)){ - createIssueComment(commit) + if(issueCount > 0) { + createIssueComment(commit) + } Some(commit) } else None } @@ -158,10 +168,13 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: } // close issues - val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch - if(refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE){ - git.log.addRange(command.getOldId, command.getNewId).call.asScala.foreach { commit => - closeIssuesFromMessage(commit.getFullMessage, pusher, owner, repository) + if(issueCount > 0) { + val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch + if (refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE) { + git.log.addRange(command.getOldId, command.getNewId).call.asScala.foreach { + commit => + closeIssuesFromMessage(commit.getFullMessage, pusher, owner, repository) + } } } From 5d3365a944f6696e602e88b87634bca382597cc1 Mon Sep 17 00:00:00 2001 From: takezoe Date: Mon, 31 Mar 2014 00:39:30 +0900 Subject: [PATCH 13/76] Fix #328 --- .../scala/app/RepositorySettingsController.scala | 2 +- src/main/twirl/settings/options.scala.html | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/scala/app/RepositorySettingsController.scala b/src/main/scala/app/RepositorySettingsController.scala index 3f5489a96..92b436029 100644 --- a/src/main/scala/app/RepositorySettingsController.scala +++ b/src/main/scala/app/RepositorySettingsController.scala @@ -72,7 +72,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { repository.owner, repository.name, form.description, - form.defaultBranch, + if(repository.branchList.isEmpty) "master" else form.defaultBranch, repository.repository.parentUserName.map { _ => repository.repository.isPrivate } getOrElse form.isPrivate diff --git a/src/main/twirl/settings/options.scala.html b/src/main/twirl/settings/options.scala.html index 5c7657888..73a778083 100644 --- a/src/main/twirl/settings/options.scala.html +++ b/src/main/twirl/settings/options.scala.html @@ -20,11 +20,18 @@
- + @if(repository.branchList.isEmpty){ + + } else { + @repository.branchList.map { branch => + @branch + } } + @if(repository.branchList.isEmpty){ + + }
From 6227a4643a44ef0153b8b44cb2cb5fb8722d9ef8 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 1 Apr 2014 04:47:08 +0900 Subject: [PATCH 14/76] Fix #331 --- src/main/scala/app/SystemSettingsController.scala | 4 ++++ src/main/scala/ssh/SshServerListener.scala | 1 + 2 files changed, 5 insertions(+) diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index 478c7c59e..93036ebe8 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -55,6 +55,10 @@ trait SystemSettingsControllerBase extends ControllerBase { post("/admin/system", form)(adminOnly { form => saveSystemSettings(form) + if(form.ssh && SshServer.isActive && context.settings.sshPort != form.sshPort){ + SshServer.stop() + } + if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){ SshServer.start(request.getServletContext, form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), diff --git a/src/main/scala/ssh/SshServerListener.scala b/src/main/scala/ssh/SshServerListener.scala index f8e08fdb0..b9bfb7c44 100644 --- a/src/main/scala/ssh/SshServerListener.scala +++ b/src/main/scala/ssh/SshServerListener.scala @@ -31,6 +31,7 @@ object SshServer { def stop() = { if(active.compareAndSet(true, false)){ server.stop(true) + logger.info("SSH Server is stopped.") } } From c1bbec2a1c213c8d82b13d641de289a4b31c3ccb Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 1 Apr 2014 04:53:32 +0900 Subject: [PATCH 15/76] Use logger instead of println --- src/main/scala/ssh/SshServerListener.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/scala/ssh/SshServerListener.scala b/src/main/scala/ssh/SshServerListener.scala index b9bfb7c44..59cafe3ab 100644 --- a/src/main/scala/ssh/SshServerListener.scala +++ b/src/main/scala/ssh/SshServerListener.scala @@ -46,16 +46,17 @@ object SshServer { */ class SshServerListener extends ServletContextListener with SystemSettingsService { + private val logger = LoggerFactory.getLogger(classOf[SshServerListener]) + override def contextInitialized(sce: ServletContextEvent): Unit = { val settings = loadSystemSettings() if(settings.ssh){ - if(settings.baseUrl.isEmpty){ - // TODO use logger? - println("Could not start SshServer because the baseUrl is not configured.") - } else { - SshServer.start(sce.getServletContext, - settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), - settings.baseUrl.get) + settings.baseUrl match { + case None => + logger.error("Could not start SshServer because the baseUrl is not configured.") + case Some(baseUrl) => + SshServer.start(sce.getServletContext, + settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), baseUrl) } } } From b14917e2c67195725c09ecc3d53b5c6d20d73c25 Mon Sep 17 00:00:00 2001 From: Mike Slinn Date: Mon, 31 Mar 2014 22:43:29 -0700 Subject: [PATCH 16/76] Update README.md --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index f47318c32..f50c7d089 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,24 @@ To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored i For Installation on Windows Server with IIS see [this wiki page](https://github.com/takezoe/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, copy the [gitbucket.plist](https://raw.github.com/takezoe/gitbucket/master/contrib/macosx/gitbucket.plist) file to `~/Library/LaunchAgents/` Run the following commands in `Terminal` to From 9a47c4a99056ed28073dd8152732e0c21fd94400 Mon Sep 17 00:00:00 2001 From: takezoe Date: Thu, 3 Apr 2014 00:32:15 +0900 Subject: [PATCH 17/76] Fix #334 --- src/main/scala/service/AccountService.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index c57d0ce06..747d4d5eb 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -44,7 +44,14 @@ trait AccountService { logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..") defaultAuthentication(userName, password) } - case None => createAccount(userName, "", ldapUserInfo.fullName, ldapUserInfo.mailAddress, false, None) + case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { + case Some(x) if(!x.isRemoved) => updateAccount(x.copy(userName = userName, fullName = ldapUserInfo.fullName)) + case Some(x) if(x.isRemoved) => { + logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..") + defaultAuthentication(userName, password) + } + case None => createAccount(userName, "", ldapUserInfo.fullName, ldapUserInfo.mailAddress, false, None) + } } getAccountByUserName(userName) } From a6a08d13e9a7bff6e8b84c50aa1569e597b356da Mon Sep 17 00:00:00 2001 From: takezoe Date: Thu, 3 Apr 2014 06:54:56 +0900 Subject: [PATCH 18/76] (refs #335)Use string before '@' of mail address if user name is mail address in LDAP authentication. --- src/main/scala/util/LDAPUtil.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/scala/util/LDAPUtil.scala b/src/main/scala/util/LDAPUtil.scala index 05a717167..cf6ffe7ad 100644 --- a/src/main/scala/util/LDAPUtil.scala +++ b/src/main/scala/util/LDAPUtil.scala @@ -49,7 +49,7 @@ object LDAPUtil { ){ conn => findMailAddress(conn, userDN, ldapSettings.mailAttribute) match { case Some(mailAddress) => Right(LDAPUserInfo( - userName = userName, + userName = getUserNameFromMailAddress(userName), fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute => findFullName(conn, userDN, fullNameAttribute) }.getOrElse(userName), @@ -59,6 +59,13 @@ object LDAPUtil { } } + private def getUserNameFromMailAddress(userName: String): String = { + (userName.indexOf('@') match { + case i if i < 0 => userName.substring(0, i) + case i => userName + }).replaceAll("[^a-zA-Z0-9\\-_.]", "").replaceAll("^[_\\-]", "") + } + private def bind[A](host: String, port: Int, dn: String, password: String, tls: Boolean, keystore: String, error: String) (f: LDAPConnection => Either[String, A]): Either[String, A] = { if (tls) { From 1ec825050d7853c841a0aae36e02ed063c586ad9 Mon Sep 17 00:00:00 2001 From: takezoe Date: Thu, 3 Apr 2014 08:47:42 +0900 Subject: [PATCH 19/76] (refs #335)Use string before '@' of mail address if user name is mail address in LDAP authentication. --- src/main/scala/service/AccountService.scala | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index 747d4d5eb..590463f65 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -34,26 +34,34 @@ trait AccountService { /** * Authenticate by LDAP. */ - private def ldapAuthentication(settings: SystemSettings, userName: String, password: String) = { + private def ldapAuthentication(settings: SystemSettings, userName: String, password: String): Option[Account] = { LDAPUtil.authenticate(settings.ldap.get, userName, password) match { case Right(ldapUserInfo) => { // Create or update account by LDAP information - getAccountByUserName(userName, true) match { - case Some(x) if(!x.isRemoved) => updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) + getAccountByUserName(ldapUserInfo.userName, true) match { + case Some(x) if(!x.isRemoved) => { + updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) + getAccountByUserName(ldapUserInfo.userName) + } case Some(x) if(x.isRemoved) => { logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..") defaultAuthentication(userName, password) } case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { - case Some(x) if(!x.isRemoved) => updateAccount(x.copy(userName = userName, fullName = ldapUserInfo.fullName)) + case Some(x) if(!x.isRemoved) => { + updateAccount(x.copy(userName = ldapUserInfo.userName, fullName = ldapUserInfo.fullName)) + getAccountByUserName(ldapUserInfo.userName) + } case Some(x) if(x.isRemoved) => { logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..") defaultAuthentication(userName, password) } - case None => createAccount(userName, "", ldapUserInfo.fullName, ldapUserInfo.mailAddress, false, None) + case None => { + createAccount(ldapUserInfo.userName, "", ldapUserInfo.fullName, ldapUserInfo.mailAddress, false, None) + getAccountByUserName(ldapUserInfo.userName) + } } } - getAccountByUserName(userName) } case Left(errorMessage) => { logger.info(s"LDAP Authentication Failed: ${errorMessage}") @@ -90,14 +98,14 @@ trait AccountService { isGroupAccount = false, isRemoved = false) - def updateAccount(account: Account): Unit = + def updateAccount(account: Account): Unit = Accounts .filter { a => a.userName is account.userName.bind } .map { a => a.password ~ a.fullName ~ a.mailAddress ~ a.isAdmin ~ a.url.? ~ a.registeredDate ~ a.updatedDate ~ a.lastLoginDate.? ~ a.removed } .update ( - account.password, - account.fullName, - account.mailAddress, + account.password, + account.fullName, + account.mailAddress, account.isAdmin, account.url, account.registeredDate, From 91bd9d11111d613a8bb2bd0e992e6bb09b8269cc Mon Sep 17 00:00:00 2001 From: takezoe Date: Thu, 3 Apr 2014 08:55:50 +0900 Subject: [PATCH 20/76] (refs #335)Oops, fix substring condition --- src/main/scala/util/LDAPUtil.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/util/LDAPUtil.scala b/src/main/scala/util/LDAPUtil.scala index cf6ffe7ad..131a133c2 100644 --- a/src/main/scala/util/LDAPUtil.scala +++ b/src/main/scala/util/LDAPUtil.scala @@ -61,8 +61,8 @@ object LDAPUtil { private def getUserNameFromMailAddress(userName: String): String = { (userName.indexOf('@') match { - case i if i < 0 => userName.substring(0, i) - case i => userName + case i if i >= 0 => userName.substring(0, i) + case i => userName }).replaceAll("[^a-zA-Z0-9\\-_.]", "").replaceAll("^[_\\-]", "") } From b357d52ec596d96175428b45f3a1d65d100cdf47 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 7 Apr 2014 21:08:13 +0900 Subject: [PATCH 21/76] (refs #335)Fix LDAP authentication --- src/main/scala/service/AccountService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index 590463f65..d1c4d3fe0 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -40,7 +40,7 @@ trait AccountService { // Create or update account by LDAP information getAccountByUserName(ldapUserInfo.userName, true) match { case Some(x) if(!x.isRemoved) => { - updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) + updateAccount(x.copy(userName = ldapUserInfo.userName, mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) getAccountByUserName(ldapUserInfo.userName) } case Some(x) if(x.isRemoved) => { From 3a2ecf6896674b8a16b4c403026359c46da28f1f Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Mon, 7 Apr 2014 22:26:27 +0900 Subject: [PATCH 22/76] Fix bug #340 --- src/main/scala/ssh/SshUtil.scala | 5 ++++- src/main/twirl/account/ssh.scala.html | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/scala/ssh/SshUtil.scala b/src/main/scala/ssh/SshUtil.scala index db578ded2..50e1cc91f 100644 --- a/src/main/scala/ssh/SshUtil.scala +++ b/src/main/scala/ssh/SshUtil.scala @@ -28,6 +28,9 @@ object SshUtil { } } - def fingerPrint(key: String): String = KeyUtils.getFingerPrint(str2PublicKey(key).get) + def fingerPrint(key: String): Option[String] = str2PublicKey(key) match { + case Some(publicKey) => Some(KeyUtils.getFingerPrint(publicKey)) + case None => None + } } diff --git a/src/main/twirl/account/ssh.scala.html b/src/main/twirl/account/ssh.scala.html index e6ee34c55..8f37ace26 100644 --- a/src/main/twirl/account/ssh.scala.html +++ b/src/main/twirl/account/ssh.scala.html @@ -17,7 +17,7 @@ @if(i != 0){
} - @key.title (@_root_.ssh.SshUtil.fingerPrint(key.publicKey)) + @key.title (@_root_.ssh.SshUtil.fingerPrint(key.publicKey).getOrElse("Key is invalid.")) Delete }
@@ -42,4 +42,4 @@ -} \ No newline at end of file +} From 06978a4fc4141cc68088aa40dc5e2c9764d62a37 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Mon, 7 Apr 2014 22:56:13 +0900 Subject: [PATCH 23/76] (refs #340)Add public key validation --- src/main/scala/app/AccountController.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index d19af7eaa..d0a0a896b 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -5,6 +5,7 @@ import util._ import util.StringUtil._ import util.Directory._ import util.ControlUtil._ +import ssh.SshUtil import jp.sf.amateras.scalatra.forms._ import org.apache.commons.io.FileUtils import org.scalatra.i18n.Messages @@ -49,7 +50,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { val sshKeyForm = mapping( "title" -> trim(label("Title", text(required, maxlength(100)))), - "publicKey" -> trim(label("Key" , text(required))) + "publicKey" -> trim(label("Key" , text(required, validPublicKey))) )(SshKeyForm.apply) case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String) @@ -435,4 +436,11 @@ trait AccountControllerBase extends AccountManagementControllerBase { }) None else Some("Must select one manager at least.") } } + + private def validPublicKey: Constraint = new Constraint(){ + override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match { + case Some(_) => None + case None => Some("Key is invalid.") + } + } } From dfa48166339a1153d62ea709bd33aff649de3fd6 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 8 Apr 2014 10:13:07 +0900 Subject: [PATCH 24/76] (refs #288)Once remove AsciiDoc support --- project/build.scala | 4 +- src/main/scala/view/Asciidoc.scala | 62 ------------------------------ src/main/scala/view/helpers.scala | 8 +--- 3 files changed, 2 insertions(+), 72 deletions(-) delete mode 100644 src/main/scala/view/Asciidoc.scala diff --git a/project/build.scala b/project/build.scala index 9d2e16d0e..13e306915 100644 --- a/project/build.scala +++ b/project/build.scala @@ -44,9 +44,7 @@ object MyBuild extends Build { "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime", "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided", "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"), - "junit" % "junit" % "4.11" % "test", - "org.asciidoctor" % "asciidoctor-java-integration" % "0.1.4", - "net.sourceforge.htmlcleaner" % "htmlcleaner" % "2.7" + "junit" % "junit" % "4.11" % "test" ), EclipseKeys.withSource := true, javacOptions in compile ++= Seq("-target", "6", "-source", "6"), diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala deleted file mode 100644 index 5c131c491..000000000 --- a/src/main/scala/view/Asciidoc.scala +++ /dev/null @@ -1,62 +0,0 @@ -package view - -import org.asciidoctor.Asciidoctor -import org.asciidoctor.AttributesBuilder -import org.asciidoctor.OptionsBuilder -import org.asciidoctor.SafeMode -import org.htmlcleaner.HtmlCleaner -import org.htmlcleaner.HtmlNode -import org.htmlcleaner.SimpleHtmlSerializer -import org.htmlcleaner.TagNode -import org.htmlcleaner.TagNodeVisitor - -object Asciidoc { - - private[this] lazy val asciidoctor = Asciidoctor.Factory.create() - - /** - * Converts Markdown of Wiki pages to HTML. - */ - def toHtml(filePath: List[String], asciidoc: String, branch: String, repository: service.RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = { - - val options = OptionsBuilder.options() - options.safe(SafeMode.SECURE) - val attributes = AttributesBuilder.attributes() - attributes.showTitle(true) - options.attributes(attributes.get()) - val rendered = asciidoctor.render(asciidoc, options) - - val path = filePath.reverse.tail.reverse match { - case Nil => "" - case p => p.mkString("", "/", "/") - } - val relativeUrlPrefix = s"${helpers.url(repository)}/blob/${branch}/${path}" - prefixRelativeUrls(rendered, relativeUrlPrefix) - } - - private[this] val exceptionPrefixes = Seq("#", "/", "http://", "https://") - - def prefixRelativeUrls(html: String, urlPrefix: String): String = { - val cleaner = new HtmlCleaner() - val node = cleaner.clean(html) - node.traverse(new TagNodeVisitor() { - override def visit(tagNode: TagNode, htmlNode: HtmlNode): Boolean = { - htmlNode match { - case tag: TagNode if tag.getName == "a" => - Option(tag.getAttributeByName("href")) foreach { href => - if (exceptionPrefixes.forall(p => !href.startsWith(p))) { - tag.addAttribute("href", s"${urlPrefix}${href}") - } - } - case _ => - } - // continue traversal - true - } - }) - new SimpleHtmlSerializer(cleaner.getProperties()).getAsString(node) - } - -} - diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index feb5f21fb..8e8a61f5e 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -30,9 +30,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache private[this] val renderersBySuffix: Seq[(String, (List[String], String, String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = Seq( ".md" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".markdown" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".adoc" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)), - ".asciidoc" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)) + ".markdown" -> ((filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)) ) def renderableSuffixes: Seq[String] = renderersBySuffix.map(_._1) @@ -59,10 +57,6 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache } } - def asciidoc(filePath: List[String], value: String, branch: String, repository: service.RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = - Html(Asciidoc.toHtml(filePath, value, branch, repository, enableWikiLink, enableRefsLink)) - /** * Returns <img> which displays the avatar icon for the given user name. * This method looks up Gravatar if avatar icon has not been configured in user settings. From c3ac0f3d9f8f400d4e9b9273d22f3390fcb6acc2 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 8 Apr 2014 16:21:10 +0900 Subject: [PATCH 25/76] (refs #335)Fix account updating in LDAP authentication --- src/main/scala/service/AccountService.scala | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index d1c4d3fe0..7979fa24b 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -40,7 +40,7 @@ trait AccountService { // Create or update account by LDAP information getAccountByUserName(ldapUserInfo.userName, true) match { case Some(x) if(!x.isRemoved) => { - updateAccount(x.copy(userName = ldapUserInfo.userName, mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) + updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) getAccountByUserName(ldapUserInfo.userName) } case Some(x) if(x.isRemoved) => { @@ -49,7 +49,7 @@ trait AccountService { } case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { case Some(x) if(!x.isRemoved) => { - updateAccount(x.copy(userName = ldapUserInfo.userName, fullName = ldapUserInfo.fullName)) + updateAccountByMailAddress(x.copy(userName = ldapUserInfo.userName, fullName = ldapUserInfo.fullName)) getAccountByUserName(ldapUserInfo.userName) } case Some(x) if(x.isRemoved) => { @@ -113,6 +113,21 @@ trait AccountService { account.lastLoginDate, account.isRemoved) + def updateAccountByMailAddress(account: Account): Unit = + Accounts + .filter { a => a.mailAddress is account.mailAddress.bind } + .map { a => a.userName ~ a.password ~ a.fullName ~ a.isAdmin ~ a.url.? ~ a.registeredDate ~ a.updatedDate ~ a.lastLoginDate.? ~ a.removed } + .update ( + account.userName, + account.password, + account.fullName, + account.isAdmin, + account.url, + account.registeredDate, + currentDate, + account.lastLoginDate, + account.isRemoved) + def updateAvatarImage(userName: String, image: Option[String]): Unit = Accounts.filter(_.userName is userName.bind).map(_.image.?).update(image) From cc4fb8bf79418936746ff8231bd3484dc1154980 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 8 Apr 2014 16:24:53 +0900 Subject: [PATCH 26/76] (refs #335)Add Scaladoc --- src/main/scala/service/AccountService.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index 7979fa24b..df71431dd 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -98,6 +98,9 @@ trait AccountService { isGroupAccount = false, isRemoved = false) + /** + * Update the account by user name. + */ def updateAccount(account: Account): Unit = Accounts .filter { a => a.userName is account.userName.bind } @@ -113,6 +116,9 @@ trait AccountService { account.lastLoginDate, account.isRemoved) + /** + * Update the account by mail address + */ def updateAccountByMailAddress(account: Account): Unit = Accounts .filter { a => a.mailAddress is account.mailAddress.bind } From c128086778895307f2f1eadf10f4d7eb23d8d6f1 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 8 Apr 2014 16:34:01 +0900 Subject: [PATCH 27/76] (refs #335)Revert account updating in LDAP authentication --- src/main/scala/service/AccountService.scala | 23 +-------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index df71431dd..acd76c9d1 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -49,7 +49,7 @@ trait AccountService { } case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { case Some(x) if(!x.isRemoved) => { - updateAccountByMailAddress(x.copy(userName = ldapUserInfo.userName, fullName = ldapUserInfo.fullName)) + updateAccount(x.copy(fullName = ldapUserInfo.fullName)) getAccountByUserName(ldapUserInfo.userName) } case Some(x) if(x.isRemoved) => { @@ -98,9 +98,6 @@ trait AccountService { isGroupAccount = false, isRemoved = false) - /** - * Update the account by user name. - */ def updateAccount(account: Account): Unit = Accounts .filter { a => a.userName is account.userName.bind } @@ -116,24 +113,6 @@ trait AccountService { account.lastLoginDate, account.isRemoved) - /** - * Update the account by mail address - */ - def updateAccountByMailAddress(account: Account): Unit = - Accounts - .filter { a => a.mailAddress is account.mailAddress.bind } - .map { a => a.userName ~ a.password ~ a.fullName ~ a.isAdmin ~ a.url.? ~ a.registeredDate ~ a.updatedDate ~ a.lastLoginDate.? ~ a.removed } - .update ( - account.userName, - account.password, - account.fullName, - account.isAdmin, - account.url, - account.registeredDate, - currentDate, - account.lastLoginDate, - account.isRemoved) - def updateAvatarImage(userName: String, image: Option[String]): Unit = Accounts.filter(_.userName is userName.bind).map(_.image.?).update(image) From 8fbbe7f31e232e0bc92a8fc0c669c677f2eb2e8f Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 10 Apr 2014 11:25:39 +0900 Subject: [PATCH 28/76] (refs #337)Fix JavaScript not found problem in JBoss/WildFly --- src/main/scala/ScalatraBootstrap.scala | 3 +- .../webapp/assets/common/js/validation.js | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/main/webapp/assets/common/js/validation.js diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index 21c213d49..90fe41548 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,6 +1,6 @@ import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter} import app._ -import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider +//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider import org.scalatra._ import javax.servlet._ import java.util.EnumSet @@ -28,7 +28,6 @@ class ScalatraBootstrap extends LifeCycle { context.mount(new IssuesController, "/*") context.mount(new PullRequestsController, "/*") context.mount(new RepositorySettingsController, "/*") - context.mount(new ValidationJavaScriptProvider, "/assets/common/js/*") // Create GITBUCKET_HOME directory if it does not exist val dir = new java.io.File(_root_.util.Directory.GitBucketHome) diff --git a/src/main/webapp/assets/common/js/validation.js b/src/main/webapp/assets/common/js/validation.js new file mode 100644 index 000000000..135fb82cc --- /dev/null +++ b/src/main/webapp/assets/common/js/validation.js @@ -0,0 +1,40 @@ +$(function(){ + $.each($('form[validate=true]'), function(i, form){ + $(form).submit(validate); + }); + $.each($('input[formaction]'), function(i, input){ + $(input).click(function(){ + var form = $(input).parents('form') + $(form).attr('action', $(input).attr('formaction')) + }); + }); +}); + +function validate(e){ + var form = $(e.target); + + if(form.data('validated') == true){ + return true; + } + + $.post(form.attr('action') + '/validate', $(e.target).serialize(), function(data){ + // clear all error messages + $('.error').text(''); + + if($.isEmptyObject(data)){ + form.data('validated', true); + form.submit(); + form.data('validated', false); + } else { + form.data('validated', false); + displayErrors(data); + } + }, 'json'); + return false; +} + +function displayErrors(data){ + $.each(data, function(key, value){ + $('#error-' + key.split(".").join("_")).text(value); + }); +} \ No newline at end of file From 3f76453f34513b4612e38ba6da419dc1e7e9349c Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 10 Apr 2014 17:42:25 +0900 Subject: [PATCH 29/76] (refs #343)Retrieve all commit id from Git repository in pre commit hook instead of COMMIT_LOG table --- src/main/scala/app/AccountController.scala | 14 ----- .../scala/app/PullRequestsController.scala | 24 +++------ src/main/scala/model/Activity.scala | 6 --- src/main/scala/service/ActivityService.scala | 15 +----- .../scala/service/RepositoryService.scala | 3 -- .../scala/servlet/GitRepositoryServlet.scala | 51 ++++++++++--------- src/main/scala/util/JGitUtil.scala | 12 +++++ 7 files changed, 45 insertions(+), 80 deletions(-) diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index d0a0a896b..0a2b62a95 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -385,20 +385,6 @@ trait AccountControllerBase extends AccountManagementControllerBase { getWikiRepositoryDir(repository.owner, repository.name), getWikiRepositoryDir(loginUserName, repository.name)) - // insert commit id - using(Git.open(getRepositoryDir(loginUserName, repository.name))){ git => - JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch => - JGitUtil.getCommitLog(git, branch) match { - case Right((commits, _)) => commits.foreach { commit => - if(!existsCommitId(loginUserName, repository.name, commit.id)){ - insertCommitId(loginUserName, repository.name, commit.id) - } - } - case Left(_) => ??? - } - } - } - // Record activity recordForkActivity(repository.owner, repository.name, loginUserName) // redirect to the repository diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index 47c061430..384ad34cf 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -177,12 +177,6 @@ trait PullRequestsControllerBase extends ControllerBase { val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom, pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo) - commits.flatten.foreach { commit => - if(!existsCommitId(owner, name, commit.id)){ - insertCommitId(owner, name, commit.id) - } - } - // close issue by content of pull request val defaultBranch = getRepository(owner, name, baseUrl).get.repository.defaultBranch if(pullreq.branch == defaultBranch){ @@ -438,24 +432,19 @@ trait PullRequestsControllerBase extends ControllerBase { (defaultOwner, value) } - /** - * Extracts all repository names from [[service.RepositoryService.RepositoryTreeNode]] as flat list. - */ - private def getRepositoryNames(node: RepositoryTreeNode): List[String] = - node.owner :: node.children.map { child => getRepositoryNames(child) }.flatten - /** * Returns the identifier of the root commit (or latest merge commit) of the specified branch. */ private def getForkedCommitId(oldGit: Git, newGit: Git, userName: String, repositoryName: String, branch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String): String = - JGitUtil.getCommitLogs(newGit, requestBranch, true){ commit => - existsCommitId(userName, repositoryName, commit.getName) && JGitUtil.getBranchesOfCommit(oldGit, commit.getName).contains(branch) - }.head.id + defining(JGitUtil.getAllCommitIds(oldGit)){ existIds => + JGitUtil.getCommitLogs(newGit, requestBranch, true) { commit => + existIds.contains(commit.name) && JGitUtil.getBranchesOfCommit(oldGit, commit.getName).contains(branch) + }.head.id + } private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String, - requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = { - + requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = using( Git.open(getRepositoryDir(userName, repositoryName)), Git.open(getRepositoryDir(requestUserName, requestRepositoryName)) @@ -473,7 +462,6 @@ trait PullRequestsControllerBase extends ControllerBase { (commits, diffs) } - } private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = defining(repository.owner, repository.name){ case (owner, repoName) => diff --git a/src/main/scala/model/Activity.scala b/src/main/scala/model/Activity.scala index 9c2ae025c..b9d5bb5e3 100644 --- a/src/main/scala/model/Activity.scala +++ b/src/main/scala/model/Activity.scala @@ -13,12 +13,6 @@ object Activities extends Table[Activity]("ACTIVITY") with BasicTemplate { def autoInc = userName ~ repositoryName ~ activityUserName ~ activityType ~ message ~ additionalInfo.? ~ activityDate returning activityId } -object CommitLog extends Table[(String, String, String)]("COMMIT_LOG") with BasicTemplate { - def commitId = column[String]("COMMIT_ID") - def * = userName ~ repositoryName ~ commitId - def byPrimaryKey(userName: String, repositoryName: String, commitId: String) = byRepository(userName, repositoryName) && (this.commitId is commitId.bind) -} - case class Activity( activityId: Int, userName: String, diff --git a/src/main/scala/service/ActivityService.scala b/src/main/scala/service/ActivityService.scala index fce0b095b..64fbb4e86 100644 --- a/src/main/scala/service/ActivityService.scala +++ b/src/main/scala/service/ActivityService.scala @@ -153,19 +153,6 @@ trait ActivityService { Some(message), currentDate) - def insertCommitId(userName: String, repositoryName: String, commitId: String) = { - CommitLog insert (userName, repositoryName, commitId) - } - - def insertAllCommitIds(userName: String, repositoryName: String, commitIds: List[String]) = - CommitLog insertAll (commitIds.map(commitId => (userName, repositoryName, commitId)): _*) - - def getAllCommitIds(userName: String, repositoryName: String): List[String] = - Query(CommitLog).filter(_.byRepository(userName, repositoryName)).map(_.commitId).list - - def existsCommitId(userName: String, repositoryName: String, commitId: String): Boolean = - Query(CommitLog).filter(_.byPrimaryKey(userName, repositoryName, commitId)).firstOption.isDefined - - private def cut(value: String, length: Int): String = + private def cut(value: String, length: Int): String = if(value.length > length) value.substring(0, length) + "..." else value } diff --git a/src/main/scala/service/RepositoryService.scala b/src/main/scala/service/RepositoryService.scala index 38e7606b1..774017e09 100644 --- a/src/main/scala/service/RepositoryService.scala +++ b/src/main/scala/service/RepositoryService.scala @@ -52,7 +52,6 @@ trait RepositoryService { self: AccountService => val issueComments = Query(IssueComments).filter(_.byRepository(oldUserName, oldRepositoryName)).list val issueLabels = Query(IssueLabels ).filter(_.byRepository(oldUserName, oldRepositoryName)).list val collaborators = Query(Collaborators).filter(_.byRepository(oldUserName, oldRepositoryName)).list - val commitLog = Query(CommitLog ).filter(_.byRepository(oldUserName, oldRepositoryName)).list val activities = Query(Activities ).filter(_.byRepository(oldUserName, oldRepositoryName)).list Repositories.filter { t => @@ -78,7 +77,6 @@ trait RepositoryService { self: AccountService => Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) IssueLabels .insertAll(issueLabels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) Collaborators .insertAll(collaborators .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - CommitLog .insertAll(commitLog .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*) Activities .insertAll(activities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) // Update activity messages @@ -102,7 +100,6 @@ trait RepositoryService { self: AccountService => def deleteRepository(userName: String, repositoryName: String): Unit = { Activities .filter(_.byRepository(userName, repositoryName)).delete - CommitLog .filter(_.byRepository(userName, repositoryName)).delete Collaborators .filter(_.byRepository(userName, repositoryName)).delete IssueLabels .filter(_.byRepository(userName, repositoryName)).delete Labels .filter(_.byRepository(userName, repositoryName)).delete diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala index d02cb2f5e..389c1af2b 100644 --- a/src/main/scala/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/servlet/GitRepositoryServlet.scala @@ -81,7 +81,9 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] logger.debug("repository:" + owner + "/" + repository) if(!repository.endsWith(".wiki")){ - receivePack.setPostReceiveHook(new CommitLogHook(owner, repository, pusher, baseUrl(request))) + val hook = new CommitLogHook(owner, repository, pusher, baseUrl(request)) + receivePack.setPreReceiveHook(hook) + receivePack.setPostReceiveHook(hook) } receivePack } @@ -90,11 +92,25 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] import scala.collection.JavaConverters._ -class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String) extends PostReceiveHook +class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String) extends PostReceiveHook with PreReceiveHook with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService { private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) - + private var existIds: Seq[String] = Nil + + def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = { + try { + using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => + existIds = JGitUtil.getAllCommitIds(git) + } + } catch { + case ex: Exception => { + logger.error(ex.toString, ex) + throw ex + } + } + } + def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = { try { using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => @@ -117,30 +133,15 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: countIssue(IssueSearchCondition(state = "closed"), Map.empty, false, owner -> repository) // Extract new commit and apply issue comment - val newCommits = if(commits.size > 1000){ - val existIds = getAllCommitIds(owner, repository) - commits.flatMap { commit => - if(!existIds.contains(commit.id)){ - if(issueCount > 0) { - createIssueComment(commit) - } - Some(commit) - } else None - } - } else { - commits.flatMap { commit => - if(!existsCommitId(owner, repository, commit.id)){ - if(issueCount > 0) { - createIssueComment(commit) - } - Some(commit) - } else None - } + val newCommits = commits.flatMap { commit => + if (!existIds.contains(commit.id)) { + if (issueCount > 0) { + createIssueComment(commit) + } + Some(commit) + } else None } - // batch insert all new commit id - insertAllCommitIds(owner, repository, newCommits.map(_.id)) - // record activity if(refName(1) == "heads"){ command.getType match { diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 5ab4bf76f..4d89a3ec5 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -570,4 +570,16 @@ object JGitUtil { case e: MissingObjectException => None } + /** + * Returns all commit id in the specified repository. + */ + def getAllCommitIds(git: Git): Seq[String] = { + val existIds = new scala.collection.mutable.ListBuffer[String]() + val i = git.log.all.call.iterator + while(i.hasNext){ + existIds += i.next.name + } + existIds.toSeq + } + } From 2ae7798591470dca35a2e4febc52aca2a0f90907 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 10 Apr 2014 17:55:58 +0900 Subject: [PATCH 30/76] (refs #343)Apply fix to ssh pushing also --- src/main/scala/ssh/GitCommand.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/scala/ssh/GitCommand.scala b/src/main/scala/ssh/GitCommand.scala index 9f014f0e7..ffc9ccc39 100644 --- a/src/main/scala/ssh/GitCommand.scala +++ b/src/main/scala/ssh/GitCommand.scala @@ -106,7 +106,9 @@ class GitReceivePack(context: ServletContext, owner: String, repoName: String, b val repository = git.getRepository val receive = new ReceivePack(repository) if(!repoName.endsWith(".wiki")){ - receive.setPostReceiveHook(new CommitLogHook(owner, repoName, user, baseUrl)) + val hook = new CommitLogHook(owner, repoName, user, baseUrl) + receive.setPreReceiveHook(hook) + receive.setPostReceiveHook(hook) } receive.receive(in, out, err) } From 7b7c0e1eee2f8cc00081af903921576b090a4664 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Fri, 11 Apr 2014 01:28:43 +0900 Subject: [PATCH 31/76] Fix getAllcommitIds bug in empty repository --- src/main/scala/util/JGitUtil.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 4d89a3ec5..b15a10a4d 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -573,7 +573,9 @@ object JGitUtil { /** * Returns all commit id in the specified repository. */ - def getAllCommitIds(git: Git): Seq[String] = { + def getAllCommitIds(git: Git): Seq[String] = if(isEmpty(git)) { + Nil + } else { val existIds = new scala.collection.mutable.ListBuffer[String]() val i = git.log.all.call.iterator while(i.hasNext){ From d7eef8bd257327f157646efcdb36fcf32f7a6c05 Mon Sep 17 00:00:00 2001 From: takezoe Date: Fri, 11 Apr 2014 07:40:07 +0900 Subject: [PATCH 32/76] (refs #343)Add drop COMMIT_LOG table statement in migration for 1.13 --- src/main/resources/update/1_13.sql | 1 + src/main/scala/servlet/AutoUpdateListener.scala | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/main/resources/update/1_13.sql diff --git a/src/main/resources/update/1_13.sql b/src/main/resources/update/1_13.sql new file mode 100644 index 000000000..ed26f65ac --- /dev/null +++ b/src/main/resources/update/1_13.sql @@ -0,0 +1 @@ +DROP TABLE COMMIT_LOG; \ No newline at end of file diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index 8cafb6059..9ffed838e 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -50,6 +50,7 @@ object AutoUpdate { * The history of versions. A head of this sequence is the current BitBucket version. */ val versions = Seq( + Version(1, 13), Version(1, 12), Version(1, 11), Version(1, 10), From 9c4f7cc530bd27a6db5eba13262c9d0943577483 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 11 Apr 2014 11:57:16 +0900 Subject: [PATCH 33/76] Fix message for empty repository --- .../app/RepositoryViewerController.scala | 2 +- src/main/scala/util/JGitUtil.scala | 2 +- src/main/twirl/repo/files.scala.html | 2 +- src/main/twirl/repo/guide.scala.html | 39 +++++++++++-------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index dda0e6147..e5ae04ae6 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -264,7 +264,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { */ private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = { if(repository.commitCount == 0){ - repo.html.guide(repository) + repo.html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount)) } else { using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => //val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head) diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 4d89a3ec5..cd82de061 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -137,7 +137,7 @@ object JGitUtil { using(Git.open(getRepositoryDir(owner, repository))){ git => try { // get commit count - val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10000).sum + val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10001).sum RepositoryInfo( owner, repository, s"${baseUrl}/git/${owner}/${repository}.git", diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index db21928a7..96be27815 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -12,7 +12,7 @@
@repository.name / diff --git a/src/main/twirl/repo/guide.scala.html b/src/main/twirl/repo/guide.scala.html index a5c7738fe..e016b296d 100644 --- a/src/main/twirl/repo/guide.scala.html +++ b/src/main/twirl/repo/guide.scala.html @@ -1,21 +1,26 @@ -@(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) +@(repository: service.RepositoryService.RepositoryInfo, + hasWritePermission: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { -@html.header("code", repository) -

Create a new repository on the command line

-
-touch README.md
-git init
-git add README.md
-git commit -m "first commit"
-git remote add origin @repository.httpUrl
-git push -u origin master
-
+ @html.header("code", repository) + @if(!hasWritePermission){ +

This is an empty repository

+ } else { +

Create a new repository on the command line

+
+    touch README.md
+    git init
+    git add README.md
+    git commit -m "first commit"
+    git remote add origin @repository.httpUrl
+    git push -u origin master
+    
-

Push an existing repository from the command line

-
-git remote add origin @repository.httpUrl
-git push -u origin master
-
-} +

Push an existing repository from the command line

+
+    git remote add origin @repository.httpUrl
+    git push -u origin master
+    
+ } +} \ No newline at end of file From e0f1658120ae6479c04423099925f72d8859adaa Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 11 Apr 2014 12:12:24 +0900 Subject: [PATCH 34/76] Use retro as default Gravator image --- src/main/scala/view/AvatarImageProvider.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/view/AvatarImageProvider.scala b/src/main/scala/view/AvatarImageProvider.scala index 1dff9ab4a..9425d7a4f 100644 --- a/src/main/scala/view/AvatarImageProvider.scala +++ b/src/main/scala/view/AvatarImageProvider.scala @@ -17,7 +17,7 @@ trait AvatarImageProvider { self: RequestCache => // by user name getAccountByUserName(userName).map { account => if(account.image.isEmpty && context.settings.gravatar){ - s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}""" + s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { s"""${context.path}/${account.userName}/_avatar""" } @@ -28,13 +28,13 @@ trait AvatarImageProvider { self: RequestCache => // by mail address getAccountByMailAddress(mailAddress).map { account => if(account.image.isEmpty && context.settings.gravatar){ - s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}""" + s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { s"""${context.path}/${account.userName}/_avatar""" } } getOrElse { if(context.settings.gravatar){ - s"""https://www.gravatar.com/avatar/${StringUtil.md5(mailAddress.toLowerCase)}?s=${size}""" + s"""https://www.gravatar.com/avatar/${StringUtil.md5(mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { s"""${context.path}/_unknown/_avatar""" } From c9fa3291f5809bcf2b7a69d8154b1ad9d39a3954 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 11 Apr 2014 13:23:36 +0900 Subject: [PATCH 35/76] Fix TestCase --- src/test/scala/view/AvatarImageProviderSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/scala/view/AvatarImageProviderSpec.scala b/src/test/scala/view/AvatarImageProviderSpec.scala index 4a00c4fe4..a3317047b 100644 --- a/src/test/scala/view/AvatarImageProviderSpec.scala +++ b/src/test/scala/view/AvatarImageProviderSpec.scala @@ -16,7 +16,7 @@ class AvatarImageProviderSpec extends Specification { val provider = new AvatarImageProviderImpl(Some(createAccount(None))) provider.toHtml("user", 20).toString mustEqual - "" + "" } "show uploaded image even if gravatar integration is enabled" in { @@ -40,7 +40,7 @@ class AvatarImageProviderSpec extends Specification { val provider = new AvatarImageProviderImpl(None) provider.toHtml("user", 20, "hoge@hoge.com").toString mustEqual - "" + "" } "show unknown image for unknown user if gravatar integration is enabled" in { From 1db891a771e2c6e9c779b2af815ef4c7e3aa98ec Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 15 Apr 2014 19:26:09 +0900 Subject: [PATCH 36/76] (refs #345)Fix an error for users who are authenticated with mail address through LDAP in push over HTTP. --- .../servlet/BasicAuthenticationFilter.scala | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/scala/servlet/BasicAuthenticationFilter.scala b/src/main/scala/servlet/BasicAuthenticationFilter.scala index 16c516c60..c335cbcbc 100644 --- a/src/main/scala/servlet/BasicAuthenticationFilter.scala +++ b/src/main/scala/servlet/BasicAuthenticationFilter.scala @@ -3,6 +3,7 @@ package servlet import javax.servlet._ import javax.servlet.http._ import service.{SystemSettingsService, AccountService, RepositoryService} +import model.Account import org.slf4j.LoggerFactory import util.Implicits._ import util.ControlUtil._ @@ -38,9 +39,12 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou request.getHeader("Authorization") match { case null => requireAuth(response) case auth => decodeAuthHeader(auth).split(":") match { - case Array(username, password) if(isWritableUser(username, password, repository)) => { - request.setAttribute(Keys.Request.UserName, username) - chain.doFilter(req, wrappedResponse) + case Array(username, password) => getWritableUser(username, password, repository) match { + case Some(account) => { + request.setAttribute(Keys.Request.UserName, account.userName) + chain.doFilter(req, wrappedResponse) + } + case None => requireAuth(response) } case _ => requireAuth(response) } @@ -61,10 +65,10 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou } } - private def isWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo): Boolean = + private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo): Option[Account] = authenticate(loadSystemSettings(), username, password) match { - case Some(account) => hasWritePermission(repository.owner, repository.name, Some(account)) - case None => false + case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x + case _ => None } private def requireAuth(response: HttpServletResponse): Unit = { From b60e2c07c742fc7997dccaf2bdac000f374c3416 Mon Sep 17 00:00:00 2001 From: takezoe Date: Fri, 18 Apr 2014 07:04:31 +0900 Subject: [PATCH 37/76] (refs #327)Move feed.scala.xml to helper package because it breaks compilation by overriding xml package --- src/main/scala/app/IndexController.scala | 2 +- src/main/twirl/{ => helper}/feed.scala.xml | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/main/twirl/{ => helper}/feed.scala.xml (100%) diff --git a/src/main/scala/app/IndexController.scala b/src/main/scala/app/IndexController.scala index 3bc662034..a51025d4b 100644 --- a/src/main/scala/app/IndexController.scala +++ b/src/main/scala/app/IndexController.scala @@ -48,7 +48,7 @@ trait IndexControllerBase extends ControllerBase { get("/activities.atom"){ contentType = "application/atom+xml; type=feed" - xml.feed(getRecentActivities()) + helper.xml.feed(getRecentActivities()) } /** diff --git a/src/main/twirl/feed.scala.xml b/src/main/twirl/helper/feed.scala.xml similarity index 100% rename from src/main/twirl/feed.scala.xml rename to src/main/twirl/helper/feed.scala.xml From 1382d5920634199368116c477a45ada35d65874c Mon Sep 17 00:00:00 2001 From: takezoe Date: Fri, 18 Apr 2014 07:11:49 +0900 Subject: [PATCH 38/76] (refs #327)Fix icon position for global recent activities feed --- src/main/twirl/dashboard/tab.scala.html | 4 ++++ src/main/twirl/helper/activities.scala.html | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/twirl/dashboard/tab.scala.html b/src/main/twirl/dashboard/tab.scala.html index 21e4d06e3..1a4326571 100644 --- a/src/main/twirl/dashboard/tab.scala.html +++ b/src/main/twirl/dashboard/tab.scala.html @@ -1,9 +1,13 @@ @(active: String = "")(implicit context: app.Context) @import context._ +@import view.helpers._ diff --git a/src/main/twirl/helper/activities.scala.html b/src/main/twirl/helper/activities.scala.html index e1458688f..6e7f324c2 100644 --- a/src/main/twirl/helper/activities.scala.html +++ b/src/main/twirl/helper/activities.scala.html @@ -5,9 +5,6 @@ @if(activities.isEmpty){ No activity } else { -
- activities -
@activities.map { activity =>
@(activity.activityType match { From 644701d9953a904e4c1035683eb91eb4899e66d0 Mon Sep 17 00:00:00 2001 From: takezoe Date: Fri, 18 Apr 2014 07:23:52 +0900 Subject: [PATCH 39/76] (refs #327)Add atom feed of the specified user --- src/main/scala/app/AccountController.scala | 6 ++++++ src/main/twirl/account/activity.scala.html | 3 +++ src/main/twirl/account/main.scala.html | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index 0a2b62a95..ca513d451 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -119,6 +119,12 @@ trait AccountControllerBase extends AccountManagementControllerBase { } getOrElse NotFound } + get("/:userName.atom") { + val userName = params("userName") + contentType = "application/atom+xml; type=feed" + helper.xml.feed(getActivitiesByUser(userName, true)) + } + get("/:userName/_avatar"){ val userName = params("userName") getAccountByUserName(userName).flatMap(_.image).map { image => diff --git a/src/main/twirl/account/activity.scala.html b/src/main/twirl/account/activity.scala.html index 1f2cefcce..ca4c3c70c 100644 --- a/src/main/twirl/account/activity.scala.html +++ b/src/main/twirl/account/activity.scala.html @@ -2,5 +2,8 @@ @import context._ @import view.helpers._ @main(account, groupNames, "activity"){ +
+ activities +
@helper.html.activities(activities) } diff --git a/src/main/twirl/account/main.scala.html b/src/main/twirl/account/main.scala.html index 17e05c0f1..6a16f511e 100644 --- a/src/main/twirl/account/main.scala.html +++ b/src/main/twirl/account/main.scala.html @@ -28,7 +28,7 @@
-
+ @if(hasWritePermission){ +
+ Delete +
+ }
- Edit + @if(hasWritePermission){ + Edit + } Raw History
diff --git a/src/main/twirl/repo/editor.scala.html b/src/main/twirl/repo/editor.scala.html index 7061176cd..37367b09f 100644 --- a/src/main/twirl/repo/editor.scala.html +++ b/src/main/twirl/repo/editor.scala.html @@ -59,7 +59,8 @@
Cancel - + +
@@ -70,4 +71,8 @@ var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); editor.getSession().setMode("ace/mode/@editorType(pathList.last)"); + + $('#commit').click(function(){ + $('#content').val(editor.getValue()); + }); From ad244adbfa591c5df4c9daca6c2b23de171c48a6 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 24 Apr 2014 21:49:30 +0900 Subject: [PATCH 49/76] (refs #13)Commit from AceEditor is available. --- .../app/RepositoryViewerController.scala | 102 +++++++++++++----- src/main/scala/service/WikiService.scala | 2 +- src/main/scala/util/JGitUtil.scala | 7 +- src/main/twirl/repo/blob.scala.html | 5 - src/main/twirl/repo/editor.scala.html | 1 + 5 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 08bc54707..1498c257d 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -1,8 +1,9 @@ package app +import _root_.util.JGitUtil.CommitInfo import util.Directory._ import util.Implicits._ -import util.ControlUtil._ +import _root_.util.ControlUtil._ import _root_.util._ import service._ import org.scalatra._ @@ -14,21 +15,23 @@ import org.eclipse.jgit.treewalk._ import java.util.zip.{ZipEntry, ZipOutputStream} import jp.sf.amateras.scalatra.forms._ import org.eclipse.jgit.dircache.DirCache +import org.eclipse.jgit.revwalk.RevWalk -class RepositoryViewerController extends RepositoryViewerControllerBase +class RepositoryViewerController extends RepositoryViewerControllerBase with RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator /** * The repository viewer. */ -trait RepositoryViewerControllerBase extends ControllerBase { +trait RepositoryViewerControllerBase extends ControllerBase { self: RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator => - case class EditorForm(content: String, message: Option[String]) + case class EditorForm(content: String, message: Option[String], charset: String) val editorForm = mapping( "content" -> trim(label("Content", text())), - "message" -> trim(label("Messgae", optional(text()))) + "message" -> trim(label("Messgae", optional(text()))), + "charset" -> trim(label("Charset", text())) )(EditorForm.apply) /** @@ -108,14 +111,14 @@ trait RepositoryViewerControllerBase extends ControllerBase { val content = if(viewer == "other"){ if(bytes.isDefined && FileUtil.isText(bytes.get)){ // text - JGitUtil.ContentInfo("text", bytes.map(StringUtil.convertFromByteArray)) + JGitUtil.ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get))) } else { // binary - JGitUtil.ContentInfo("binary", None) + JGitUtil.ContentInfo("binary", None, None) } } else { // image or large - JGitUtil.ContentInfo(viewer, None) + JGitUtil.ContentInfo(viewer, None, None) } repo.html.editor(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit)) @@ -123,22 +126,63 @@ trait RepositoryViewerControllerBase extends ControllerBase { } }) + // TODO Share this method with WikiService + private def processTree(git: Git, id: ObjectId)(f: (String, CanonicalTreeParser) => Unit) = { + using(new RevWalk(git.getRepository)){ revWalk => + using(new TreeWalk(git.getRepository)){ treeWalk => + val index = treeWalk.addTree(revWalk.parseTree(id)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + f(treeWalk.getPathString, treeWalk.getTree(index, classOf[CanonicalTreeParser])) + } + } + } + } + post("/:owner/:repository/edit/*", editorForm)(collaboratorsOnly { (form, repository) => -// val (id, path) = splitPath(repository, multiParams("splat").head) -// val loginAccount = context.loginAccount.get -// -// using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => -// val builder = DirCache.newInCore.builder() -// val inserter = git.getRepository.newObjectInserter() -// val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") -// -// builder.add(JGitUtil.createDirCacheEntry(path, FileMode.REGULAR_FILE, -// inserter.insert(Constants.OBJ_BLOB, form.content.getBytes("UTF-8")))) // TODO charset auto detection -// builder.finish() -// -// JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), -// loginAccount.fullName, loginAccount.mailAddress, form.message.getOrElse(s"Update ${path.split("/").last}")) -// } + val (id, path) = splitPath(repository, multiParams("splat").head) + + LockUtil.lock(s"${repository.owner}/${repository.name}"){ + using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + val loginAccount = context.loginAccount.get + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headName = s"refs/heads/${id}" + val headTip = git.getRepository.resolve(s"refs/heads/${id}") + + processTree(git, headTip){ (treePath, tree) => + if(treePath != path){ + builder.add(JGitUtil.createDirCacheEntry(treePath, tree.getEntryFileMode, tree.getEntryObjectId)) + } + } + + builder.add(JGitUtil.createDirCacheEntry(path, FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, form.content.getBytes(form.charset)))) + builder.finish() + + val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), + loginAccount.fullName, loginAccount.mailAddress, form.message.getOrElse(s"Update ${path.split("/").last}")) + + inserter.flush() + inserter.release() + + // update refs + val refUpdate = git.getRepository.updateRef(headName) + refUpdate.setNewObjectId(commitId) + refUpdate.setForceUpdate(false) + refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) + //refUpdate.setRefLogMessage("merged", true) + refUpdate.update() + + // record activity + recordPushActivity(repository.owner, repository.name, loginAccount.userName, id, + List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)))) + + // TODO invoke hook + + redirect(s"/${repository.owner}/${repository.name}/blob/${id}/${path}") + } + } }) /** @@ -178,14 +222,14 @@ trait RepositoryViewerControllerBase extends ControllerBase { val content = if(viewer == "other"){ if(bytes.isDefined && FileUtil.isText(bytes.get)){ // text - JGitUtil.ContentInfo("text", bytes.map(StringUtil.convertFromByteArray)) + JGitUtil.ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get))) } else { // binary - JGitUtil.ContentInfo("binary", None) + JGitUtil.ContentInfo("binary", None, None) } } else { // image or large - JGitUtil.ContentInfo(viewer, None) + JGitUtil.ContentInfo(viewer, None, None) } repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit), @@ -311,7 +355,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { repository.repository.originRepositoryName.getOrElse(repository.name)), repository) }) - + private def splitPath(repository: service.RepositoryService.RepositoryInfo, path: String): (String, String) = { val id = repository.branchList.collectFirst { case branch if(path == branch || path.startsWith(branch + "/")) => branch @@ -327,7 +371,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { /** * Provides HTML of the file list. - * + * * @param repository the repository information * @param revstr the branch name or commit id(optional) * @param path the directory path (optional) @@ -363,5 +407,5 @@ trait RepositoryViewerControllerBase extends ControllerBase { } } } - + } diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index 870fe2084..8c742e95a 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -262,7 +262,7 @@ trait WikiService { message }) - Some(newHeadId) + Some(newHeadId.getName) } else None } } diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index ed58555f9..ef41f6be7 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -92,8 +92,9 @@ object JGitUtil { * * @param viewType "image", "large" or "other" * @param content the string content + * @param charset the character encoding */ - case class ContentInfo(viewType: String, content: Option[String]) + case class ContentInfo(viewType: String, content: Option[String], charset: Option[String]) /** * The tag data. @@ -480,7 +481,7 @@ object JGitUtil { } def createNewCommit(git: Git, inserter: ObjectInserter, headId: AnyObjectId, treeId: AnyObjectId, - fullName: String, mailAddress: String, message: String): String = { + fullName: String, mailAddress: String, message: String): ObjectId = { val newCommit = new CommitBuilder() newCommit.setCommitter(new PersonIdent(fullName, mailAddress)) newCommit.setAuthor(new PersonIdent(fullName, mailAddress)) @@ -498,7 +499,7 @@ object JGitUtil { refUpdate.setNewObjectId(newHeadId) refUpdate.update() - newHeadId.getName + newHeadId } /** diff --git a/src/main/twirl/repo/blob.scala.html b/src/main/twirl/repo/blob.scala.html index f7849fa1b..c239343c1 100644 --- a/src/main/twirl/repo/blob.scala.html +++ b/src/main/twirl/repo/blob.scala.html @@ -29,11 +29,6 @@ @datetime(latestCommit.time) @link(latestCommit.summary, repository) - @if(hasWritePermission){ -
- Delete -
- }
@if(hasWritePermission){ Edit diff --git a/src/main/twirl/repo/editor.scala.html b/src/main/twirl/repo/editor.scala.html index 37367b09f..5807988dd 100644 --- a/src/main/twirl/repo/editor.scala.html +++ b/src/main/twirl/repo/editor.scala.html @@ -60,6 +60,7 @@
Cancel +
From a104157c9a0582f9beb2ae86ecc1ccc961cfc49c Mon Sep 17 00:00:00 2001 From: takezoe Date: Fri, 25 Apr 2014 00:09:34 +0900 Subject: [PATCH 50/76] (refs #13)Disable commit button if content is not modified. --- src/main/twirl/repo/editor.scala.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/twirl/repo/editor.scala.html b/src/main/twirl/repo/editor.scala.html index 5807988dd..69f88f4bb 100644 --- a/src/main/twirl/repo/editor.scala.html +++ b/src/main/twirl/repo/editor.scala.html @@ -44,7 +44,7 @@ *@ -
@content.content.get
+
@@ -59,9 +59,10 @@
Cancel - + +
@@ -69,11 +70,17 @@ } From 0e5591017aae1c3f34b248ef5ddb142845e4fcbc Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 25 Apr 2014 10:38:34 +0900 Subject: [PATCH 51/76] Refactoring --- .../app/RepositoryViewerController.scala | 15 +---- src/main/scala/service/WikiService.scala | 65 ++++++------------- src/main/scala/util/ControlUtil.scala | 11 ---- src/main/scala/util/FileUtil.scala | 18 ----- src/main/scala/util/JGitUtil.scala | 13 ++++ 5 files changed, 34 insertions(+), 88 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 1498c257d..f3d110576 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -126,19 +126,6 @@ trait RepositoryViewerControllerBase extends ControllerBase { } }) - // TODO Share this method with WikiService - private def processTree(git: Git, id: ObjectId)(f: (String, CanonicalTreeParser) => Unit) = { - using(new RevWalk(git.getRepository)){ revWalk => - using(new TreeWalk(git.getRepository)){ treeWalk => - val index = treeWalk.addTree(revWalk.parseTree(id)) - treeWalk.setRecursive(true) - while(treeWalk.next){ - f(treeWalk.getPathString, treeWalk.getTree(index, classOf[CanonicalTreeParser])) - } - } - } - } - post("/:owner/:repository/edit/*", editorForm)(collaboratorsOnly { (form, repository) => val (id, path) = splitPath(repository, multiParams("splat").head) @@ -150,7 +137,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { val headName = s"refs/heads/${id}" val headTip = git.getRepository.resolve(s"refs/heads/${id}") - processTree(git, headTip){ (treePath, tree) => + JGitUtil.processTree(git, headTip){ (treePath, tree) => if(treePath != path){ builder.add(JGitUtil.createDirCacheEntry(treePath, tree.getEntryFileMode, tree.getEntryObjectId)) } diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala index 8c742e95a..20fa76421 100644 --- a/src/main/scala/service/WikiService.scala +++ b/src/main/scala/service/WikiService.scala @@ -175,17 +175,9 @@ trait WikiService { val inserter = git.getRepository.newObjectInserter() val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - using(new RevWalk(git.getRepository)){ revWalk => - using(new TreeWalk(git.getRepository)){ treeWalk => - val index = treeWalk.addTree(revWalk.parseTree(headId)) - treeWalk.setRecursive(true) - while(treeWalk.next){ - val path = treeWalk.getPathString - val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) - if(revertInfo.find(x => x.filePath == path).isEmpty){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } - } + JGitUtil.processTree(git, headId){ (path, tree) => + if(revertInfo.find(x => x.filePath == path).isEmpty){ + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) } } @@ -226,22 +218,14 @@ trait WikiService { var removed = false if(headId != null){ - using(new RevWalk(git.getRepository)){ revWalk => - using(new TreeWalk(git.getRepository)){ treeWalk => - val index = treeWalk.addTree(revWalk.parseTree(headId)) - treeWalk.setRecursive(true) - while(treeWalk.next){ - val path = treeWalk.getPathString - val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) - if(path == currentPageName + ".md" && currentPageName != newPageName){ - removed = true - } else if(path != newPageName + ".md"){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } else { - created = false - updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) - } - } + JGitUtil.processTree(git, headId){ (path, tree) => + if(path == currentPageName + ".md" && currentPageName != newPageName){ + removed = true + } else if(path != newPageName + ".md"){ + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } else { + created = false + updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) } } } @@ -280,26 +264,17 @@ trait WikiService { val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") var removed = false - using(new RevWalk(git.getRepository)){ revWalk => - using(new TreeWalk(git.getRepository)){ treeWalk => - val index = treeWalk.addTree(revWalk.parseTree(headId)) - treeWalk.setRecursive(true) - while(treeWalk.next){ - val path = treeWalk.getPathString - val tree = treeWalk.getTree(index, classOf[CanonicalTreeParser]) - if(path != pageName + ".md"){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } else { - removed = true - } - } - } - - if(removed){ - builder.finish() - JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message) + JGitUtil.processTree(git, headId){ (path, tree) => + if(path != pageName + ".md"){ + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } else { + removed = true } } + if(removed){ + builder.finish() + JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message) + } } } } diff --git a/src/main/scala/util/ControlUtil.scala b/src/main/scala/util/ControlUtil.scala index 0b0f7123b..c231fb01f 100644 --- a/src/main/scala/util/ControlUtil.scala +++ b/src/main/scala/util/ControlUtil.scala @@ -37,15 +37,4 @@ object ControlUtil { def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T = try f(treeWalk) finally treeWalk.release() - -// def withTmpRefSpec[T](ref: RefSpec, git: Git)(f: RefSpec => T): T = { -// try { -// f(ref) -// } finally { -// val refUpdate = git.getRepository.updateRef(ref.getDestination) -// refUpdate.setForceUpdate(true) -// refUpdate.delete() -// } -// } - } diff --git a/src/main/scala/util/FileUtil.scala b/src/main/scala/util/FileUtil.scala index e4c052f93..f3bba07f9 100644 --- a/src/main/scala/util/FileUtil.scala +++ b/src/main/scala/util/FileUtil.scala @@ -31,24 +31,6 @@ object FileUtil { def isText(content: Array[Byte]): Boolean = !content.contains(0) -// def createZipFile(dest: File, dir: File): Unit = { -// def addDirectoryToZip(out: ZipArchiveOutputStream, dir: File, path: String): Unit = { -// dir.listFiles.map { file => -// if(file.isFile){ -// out.putArchiveEntry(new ZipArchiveEntry(path + "/" + file.getName)) -// out.write(FileUtils.readFileToByteArray(file)) -// out.closeArchiveEntry -// } else if(file.isDirectory){ -// addDirectoryToZip(out, file, path + "/" + file.getName) -// } -// } -// } -// -// using(new ZipArchiveOutputStream(dest)){ out => -// addDirectoryToZip(out, dir, dir.getName) -// } -// } - def getFileName(path: String): String = defining(path.lastIndexOf('/')){ i => if(i >= 0) path.substring(i + 1) else path } diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index ef41f6be7..62887a727 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -585,4 +585,17 @@ object JGitUtil { existIds.toSeq } + def processTree(git: Git, id: ObjectId)(f: (String, CanonicalTreeParser) => Unit) = { + using(new RevWalk(git.getRepository)){ revWalk => + using(new TreeWalk(git.getRepository)){ treeWalk => + val index = treeWalk.addTree(revWalk.parseTree(id)) + treeWalk.setRecursive(true) + while(treeWalk.next){ + f(treeWalk.getPathString, treeWalk.getTree(index, classOf[CanonicalTreeParser])) + } + } + } + } + + } From 7a1c8728617841edc52ee4f7989f06b8d9c98095 Mon Sep 17 00:00:00 2001 From: Yasuhito Suganuma Date: Fri, 25 Apr 2014 23:49:17 +0900 Subject: [PATCH 52/76] Fix #308 --- src/main/scala/app/PullRequestsController.scala | 2 +- src/main/scala/app/RepositoryViewerController.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index 384ad34cf..5e2c6bcdc 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -111,7 +111,7 @@ trait PullRequestsControllerBase extends ControllerBase { val userName = context.loginAccount.get.userName if(repository.repository.defaultBranch != branchName){ using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - git.branchDelete().setBranchNames(branchName).call() + git.branchDelete().setForce(true).setBranchNames(branchName).call() recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName) } } diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index e5ae04ae6..8fed090eb 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -164,7 +164,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { val userName = context.loginAccount.get.userName if(repository.repository.defaultBranch != branchName){ using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - git.branchDelete().setBranchNames(branchName).call() + git.branchDelete().setForce(true).setBranchNames(branchName).call() recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName) } } From a97edb7ef5b5980c444c0cec21901b2c09d2be76 Mon Sep 17 00:00:00 2001 From: takezoe Date: Sun, 27 Apr 2014 02:02:36 +0900 Subject: [PATCH 53/76] (refs #13)File editing (add, edit and delete) in repository viewer is available. --- .../app/RepositoryViewerController.scala | 266 ++++++++++-------- src/main/scala/util/JGitUtil.scala | 20 ++ src/main/twirl/repo/blob.scala.html | 5 +- src/main/twirl/repo/delete.scala.html | 110 ++++++++ src/main/twirl/repo/editor.scala.html | 61 ++-- src/main/twirl/repo/files.scala.html | 2 + 6 files changed, 311 insertions(+), 153 deletions(-) create mode 100644 src/main/twirl/repo/delete.scala.html diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index f3d110576..5b3b3e219 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -15,7 +15,7 @@ import org.eclipse.jgit.treewalk._ import java.util.zip.{ZipEntry, ZipOutputStream} import jp.sf.amateras.scalatra.forms._ import org.eclipse.jgit.dircache.DirCache -import org.eclipse.jgit.revwalk.RevWalk +import org.eclipse.jgit.revwalk.{RevCommit, RevWalk} class RepositoryViewerController extends RepositoryViewerControllerBase with RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator @@ -26,14 +26,40 @@ class RepositoryViewerController extends RepositoryViewerControllerBase trait RepositoryViewerControllerBase extends ControllerBase { self: RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator => - case class EditorForm(content: String, message: Option[String], charset: String) + case class EditorForm( + branch: String, + path: String, + content: String, + message: Option[String], + charset: String, + newFileName: String, + oldFileName: Option[String] + ) + + case class DeleteForm( + branch: String, + path: String, + message: Option[String], + fileName: String + ) val editorForm = mapping( - "content" -> trim(label("Content", text())), - "message" -> trim(label("Messgae", optional(text()))), - "charset" -> trim(label("Charset", text())) + "branch" -> trim(label("Branch", text(required))), + "path" -> trim(label("Path", text())), + "content" -> trim(label("Content", text(required))), + "message" -> trim(label("Message", optional(text()))), + "charset" -> trim(label("Charset", text(required))), + "newFileName" -> trim(label("Filename", text(required))), + "oldFileName" -> trim(label("Old filename", optional(text()))) )(EditorForm.apply) + val deleteForm = mapping( + "branch" -> trim(label("Branch", text(required))), + "path" -> trim(label("Path", text())), + "message" -> trim(label("Message", optional(text()))), + "fileName" -> trim(label("Filename", text(required))) + )(DeleteForm.apply) + /** * Returns converted HTML from Markdown for preview. */ @@ -82,96 +108,68 @@ trait RepositoryViewerControllerBase extends ControllerBase { } }) - /** - * Displays the file content of the specified branch or commit. - */ + get("/:owner/:repository/new/*")(collaboratorsOnly { repository => + val (branch, path) = splitPath(repository, multiParams("splat").head) + repo.html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList, + None, JGitUtil.ContentInfo("text", None, Some("UTF-8"))) + }) + get("/:owner/:repository/edit/*")(collaboratorsOnly { repository => - val (id, path) = splitPath(repository, multiParams("splat").head) + val (branch, path) = splitPath(repository, multiParams("splat").head) using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) + val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) - @scala.annotation.tailrec - def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match { - case true if(walk.getPathString == path) => Some(walk.getObjectId(0)) - case true => getPathObjectId(path, walk) - case false => None - } - - using(new TreeWalk(git.getRepository)){ treeWalk => - treeWalk.addTree(revCommit.getTree) - treeWalk.setRecursive(true) - getPathObjectId(path, treeWalk) - } map { objectId => - // Viewer - val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize) - val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other" - val bytes = if(viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None - - val content = if(viewer == "other"){ - if(bytes.isDefined && FileUtil.isText(bytes.get)){ - // text - JGitUtil.ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get))) - } else { - // binary - JGitUtil.ContentInfo("binary", None, None) - } - } else { - // image or large - JGitUtil.ContentInfo(viewer, None, None) - } - - repo.html.editor(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit)) + getPathObjectId(git, path, revCommit).map { objectId => + val paths = path.split("/") + repo.html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last), + JGitUtil.getContentInfo(git, path, objectId)) } getOrElse NotFound } }) - post("/:owner/:repository/edit/*", editorForm)(collaboratorsOnly { (form, repository) => - val (id, path) = splitPath(repository, multiParams("splat").head) + get("/:owner/:repository/remove/*")(collaboratorsOnly { repository => + val (branch, path) = splitPath(repository, multiParams("splat").head) + using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) - LockUtil.lock(s"${repository.owner}/${repository.name}"){ - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val loginAccount = context.loginAccount.get - val builder = DirCache.newInCore.builder() - val inserter = git.getRepository.newObjectInserter() - val headName = s"refs/heads/${id}" - val headTip = git.getRepository.resolve(s"refs/heads/${id}") - - JGitUtil.processTree(git, headTip){ (treePath, tree) => - if(treePath != path){ - builder.add(JGitUtil.createDirCacheEntry(treePath, tree.getEntryFileMode, tree.getEntryObjectId)) - } - } - - builder.add(JGitUtil.createDirCacheEntry(path, FileMode.REGULAR_FILE, - inserter.insert(Constants.OBJ_BLOB, form.content.getBytes(form.charset)))) - builder.finish() - - val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), - loginAccount.fullName, loginAccount.mailAddress, form.message.getOrElse(s"Update ${path.split("/").last}")) - - inserter.flush() - inserter.release() - - // update refs - val refUpdate = git.getRepository.updateRef(headName) - refUpdate.setNewObjectId(commitId) - refUpdate.setForceUpdate(false) - refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) - //refUpdate.setRefLogMessage("merged", true) - refUpdate.update() - - // record activity - recordPushActivity(repository.owner, repository.name, loginAccount.userName, id, - List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)))) - - // TODO invoke hook - - redirect(s"/${repository.owner}/${repository.name}/blob/${id}/${path}") - } + getPathObjectId(git, path, revCommit).map { objectId => + val paths = path.split("/") + repo.html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last, + JGitUtil.getContentInfo(git, path, objectId)) + } getOrElse NotFound } }) + post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) => + commitFile(repository, form.branch, form.path, Some(form.newFileName), None, form.content, form.charset, + form.message.getOrElse(s"Create ${form.newFileName}")) + + redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ + if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}" + }") + }) + + post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) => + commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName, form.content, form.charset, + if(form.oldFileName.exists(_ == form.newFileName)){ + form.message.getOrElse(s"Update ${form.newFileName}") + } else { + form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}") + }) + + redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ + if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}" + }") + }) + + post("/:owner/:repository/remove", deleteForm)(collaboratorsOnly { (form, repository) => + commitFile(repository, form.branch, form.path, None, Some(form.fileName), "", "", + form.message.getOrElse(s"Delete ${form.fileName}")) + + redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}") + }) + /** * Displays the file content of the specified branch or commit. */ @@ -181,19 +179,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) - - @scala.annotation.tailrec - def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match { - case true if(walk.getPathString == path) => Some(walk.getObjectId(0)) - case true => getPathObjectId(path, walk) - case false => None - } - - using(new TreeWalk(git.getRepository)){ treeWalk => - treeWalk.addTree(revCommit.getTree) - treeWalk.setRecursive(true) - getPathObjectId(path, treeWalk) - } map { objectId => + getPathObjectId(git, path, revCommit).map { objectId => if(raw){ // Download defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes => @@ -201,26 +187,8 @@ trait RepositoryViewerControllerBase extends ControllerBase { bytes } } else { - // Viewer - val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize) - val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other" - val bytes = if(viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None - - val content = if(viewer == "other"){ - if(bytes.isDefined && FileUtil.isText(bytes.get)){ - // text - JGitUtil.ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get))) - } else { - // binary - JGitUtil.ContentInfo("binary", None, None) - } - } else { - // image or large - JGitUtil.ContentInfo(viewer, None, None) - } - - repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit), - hasWritePermission(repository.owner, repository.name, context.loginAccount)) + repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId), + new JGitUtil.CommitInfo(revCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount)) } } getOrElse NotFound } @@ -395,4 +363,70 @@ trait RepositoryViewerControllerBase extends ControllerBase { } } + private def commitFile(repository: service.RepositoryService.RepositoryInfo, + branch: String, path: String, newFileName: Option[String], oldFileName: Option[String], + content: String, charset: String, message: String) = { + + val newPath = newFileName.map { newFileName => if(path.length == 0) newFileName else s"${path}/${newFileName}" } + val oldPath = oldFileName.map { oldFileName => if(path.length == 0) oldFileName else s"${path}/${oldFileName}" } + + LockUtil.lock(s"${repository.owner}/${repository.name}"){ + using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + val loginAccount = context.loginAccount.get + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headName = s"refs/heads/${branch}" + val headTip = git.getRepository.resolve(s"refs/heads/${branch}") + + JGitUtil.processTree(git, headTip){ (path, tree) => + if(path != newPath && !oldPath.exists(_ == path)){ + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } + } + + newPath.foreach { newPath => + builder.add(JGitUtil.createDirCacheEntry(newPath, FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset)))) + builder.finish() + } + + val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), + loginAccount.fullName, loginAccount.mailAddress, message) + + inserter.flush() + inserter.release() + + // update refs + val refUpdate = git.getRepository.updateRef(headName) + refUpdate.setNewObjectId(commitId) + refUpdate.setForceUpdate(false) + refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) + //refUpdate.setRefLogMessage("merged", true) + refUpdate.update() + + // record activity + recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, + List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)))) + + // TODO invoke hook + + } + } + } + + private def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = { + @scala.annotation.tailrec + def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match { + case true if(walk.getPathString == path) => Some(walk.getObjectId(0)) + case true => _getPathObjectId(path, walk) + case false => None + } + + using(new TreeWalk(git.getRepository)){ treeWalk => + treeWalk.addTree(revCommit.getTree) + treeWalk.setRecursive(true) + _getPathObjectId(path, treeWalk) + } + } + } diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 62887a727..97e8c419f 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -550,6 +550,26 @@ object JGitUtil { } } + def getContentInfo(git: Git, path: String, objectId: ObjectId): ContentInfo = { + // Viewer + val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize) + val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other" + val bytes = if(viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None + + if(viewer == "other"){ + if(bytes.isDefined && FileUtil.isText(bytes.get)){ + // text + ContentInfo("text", Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get))) + } else { + // binary + ContentInfo("binary", None, None) + } + } else { + // image or large + ContentInfo(viewer, None, None) + } + } + /** * Get object content of the given object id as byte array from the Git repository. * diff --git a/src/main/twirl/repo/blob.scala.html b/src/main/twirl/repo/blob.scala.html index c239343c1..b13846b49 100644 --- a/src/main/twirl/repo/blob.scala.html +++ b/src/main/twirl/repo/blob.scala.html @@ -35,7 +35,10 @@ } Raw History - + @if(hasWritePermission){ + Delete + } + diff --git a/src/main/twirl/repo/delete.scala.html b/src/main/twirl/repo/delete.scala.html new file mode 100644 index 000000000..c0c7dff11 --- /dev/null +++ b/src/main/twirl/repo/delete.scala.html @@ -0,0 +1,110 @@ +@(branch: String, + repository: service.RepositoryService.RepositoryInfo, + pathList: List[String], + fileName: String, + content: util.JGitUtil.ContentInfo)(implicit context: app.Context) +@import context._ +@import view.helpers._ +@html.main(s"Deleting ${path} at ${fileName} - ${repository.owner}/${repository.name}", Some(repository)) { + @html.header("code", repository) + @tab(branch, repository, "files") +
+
+ @repository.name / + @pathList.zipWithIndex.map { case (section, i) => + @section / + } + @fileName + + + +
+ + + + + +
+ @fileName +
+ View +
+
+
+ + +
+
@avatar(loginAccount.get.userName, 48)
+
+
+
+ Commit changes +
+
+ +
+
+ Cancel + +
+
+
+
+} + + + + + \ No newline at end of file diff --git a/src/main/twirl/repo/editor.scala.html b/src/main/twirl/repo/editor.scala.html index 69f88f4bb..2be0590fe 100644 --- a/src/main/twirl/repo/editor.scala.html +++ b/src/main/twirl/repo/editor.scala.html @@ -1,47 +1,32 @@ @(branch: String, repository: service.RepositoryService.RepositoryInfo, pathList: List[String], - content: util.JGitUtil.ContentInfo, - latestCommit: util.JGitUtil.CommitInfo)(implicit context: app.Context) + fileName: Option[String], + content: util.JGitUtil.ContentInfo)(implicit context: app.Context) @import context._ @import view.helpers._ -@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { +@html.main(if(fileName.isEmpty) "New File" else s"Editing ${path} at ${fileName} - ${repository.owner}/${repository.name}", Some(repository)) { @html.header("code", repository) @tab(branch, repository, "files") -
- @repository.name / - @pathList.zipWithIndex.map { case (section, i) => - @if(i == pathList.length - 1){ - @section - } else { +
+ +
+ @repository.name / + @pathList.zipWithIndex.map { case (section, i) => @section / } + + + + +
+ - + - @* - - - - *@
-
- @avatar(latestCommit, 20) - @user(latestCommit.committer, latestCommit.mailAddress, "username strong") - @datetime(latestCommit.time) - @link(latestCommit.summary, repository) -
-
- Raw - History -
-
@@ -55,14 +40,14 @@ Commit changes
- +
Cancel - +
@@ -74,7 +59,9 @@ $(function(){ $('#editor').text($('#initial').val()); var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); - editor.getSession().setMode("ace/mode/@editorType(pathList.last)"); + @if(fileName.isDefined){ + editor.getSession().setMode("ace/mode/@editorType(fileName.get)"); + } editor.on('change', function(){ $('#commit').attr('disabled', editor.getValue() == $('#initial').val()); }); @@ -82,5 +69,7 @@ $(function(){ $('#commit').click(function(){ $('#content').val(editor.getValue()); }); + + }) diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 96be27815..0077f8c5e 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -19,6 +19,8 @@ @pathList.zipWithIndex.map { case (section, i) => @section / } + + +
From cfc594805b7160230281d6959126cc1186c9be89 Mon Sep 17 00:00:00 2001 From: takezoe Date: Sun, 27 Apr 2014 02:11:10 +0900 Subject: [PATCH 54/76] (refs #348)Upgrade MINA to 0.11.0 --- project/build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.scala b/project/build.scala index 13e306915..51030c630 100644 --- a/project/build.scala +++ b/project/build.scala @@ -37,7 +37,7 @@ object MyBuild extends Build { "org.apache.commons" % "commons-compress" % "1.5", "org.apache.commons" % "commons-email" % "1.3.1", "org.apache.httpcomponents" % "httpclient" % "4.3", - "org.apache.sshd" % "apache-sshd" % "0.10.0", + "org.apache.sshd" % "apache-sshd" % "0.11.0", "com.typesafe.slick" %% "slick" % "1.0.1", "com.novell.ldap" % "jldap" % "2009-10-07", "com.h2database" % "h2" % "1.3.173", From a7b4f8de8d6550a3b0dcd6f141048a7f3fc735b0 Mon Sep 17 00:00:00 2001 From: shimamoto Date: Sun, 27 Apr 2014 16:55:48 +0900 Subject: [PATCH 55/76] (refs #12) Replaced the latest version of the dropzone. --- src/main/webapp/assets/common/js/dropzone.js | 1095 ++++++++++-------- 1 file changed, 602 insertions(+), 493 deletions(-) diff --git a/src/main/webapp/assets/common/js/dropzone.js b/src/main/webapp/assets/common/js/dropzone.js index d98c8ed21..d835bb213 100644 --- a/src/main/webapp/assets/common/js/dropzone.js +++ b/src/main/webapp/assets/common/js/dropzone.js @@ -1,36 +1,22 @@ + ;(function(){ /** - * Require the given path. + * Require the module at `name`. * - * @param {String} path + * @param {String} name * @return {Object} exports * @api public */ -function require(path, parent, orig) { - var resolved = require.resolve(path); +function require(name) { + var module = require.modules[name]; + if (!module) throw new Error('failed to require "' + name + '"'); - // lookup failed - if (null == resolved) { - orig = orig || path; - parent = parent || 'root'; - var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); - err.path = orig; - err.parent = parent; - err.require = true; - throw err; - } - - var module = require.modules[resolved]; - - // perform real require() - // by invoking the module's - // registered function - if (!module.exports) { - module.exports = {}; + if (!('exports' in module) && typeof module.definition === 'function') { module.client = module.component = true; - module.call(this, module.exports, require.relative(resolved), module); + module.definition.call(this, module.exports = {}, module); + delete module.definition; } return module.exports; @@ -43,160 +29,33 @@ function require(path, parent, orig) { require.modules = {}; /** - * Registered aliases. - */ - -require.aliases = {}; - -/** - * Resolve `path`. + * Register module at `name` with callback `definition`. * - * Lookup: - * - * - PATH/index.js - * - PATH.js - * - PATH - * - * @param {String} path - * @return {String} path or null - * @api private - */ - -require.resolve = function(path) { - if (path.charAt(0) === '/') path = path.slice(1); - - var paths = [ - path, - path + '.js', - path + '.json', - path + '/index.js', - path + '/index.json' - ]; - - for (var i = 0; i < paths.length; i++) { - var path = paths[i]; - if (require.modules.hasOwnProperty(path)) return path; - if (require.aliases.hasOwnProperty(path)) return require.aliases[path]; - } -}; - -/** - * Normalize `path` relative to the current path. - * - * @param {String} curr - * @param {String} path - * @return {String} - * @api private - */ - -require.normalize = function(curr, path) { - var segs = []; - - if ('.' != path.charAt(0)) return path; - - curr = curr.split('/'); - path = path.split('/'); - - for (var i = 0; i < path.length; ++i) { - if ('..' == path[i]) { - curr.pop(); - } else if ('.' != path[i] && '' != path[i]) { - segs.push(path[i]); - } - } - - return curr.concat(segs).join('/'); -}; - -/** - * Register module at `path` with callback `definition`. - * - * @param {String} path + * @param {String} name * @param {Function} definition * @api private */ -require.register = function(path, definition) { - require.modules[path] = definition; +require.register = function (name, definition) { + require.modules[name] = { + definition: definition + }; }; /** - * Alias a module definition. + * Define a module's exports immediately with `exports`. * - * @param {String} from - * @param {String} to + * @param {String} name + * @param {Generic} exports * @api private */ -require.alias = function(from, to) { - if (!require.modules.hasOwnProperty(from)) { - throw new Error('Failed to alias "' + from + '", it does not exist'); - } - require.aliases[to] = from; -}; - -/** - * Return a require function relative to the `parent` path. - * - * @param {String} parent - * @return {Function} - * @api private - */ - -require.relative = function(parent) { - var p = require.normalize(parent, '..'); - - /** - * lastIndexOf helper. - */ - - function lastIndexOf(arr, obj) { - var i = arr.length; - while (i--) { - if (arr[i] === obj) return i; - } - return -1; - } - - /** - * The relative require() itself. - */ - - function localRequire(path) { - var resolved = localRequire.resolve(path); - return require(resolved, parent, path); - } - - /** - * Resolve relative to the parent. - */ - - localRequire.resolve = function(path) { - var c = path.charAt(0); - if ('/' == c) return path.slice(1); - if ('.' == c) return require.normalize(p, path); - - // resolve deps by returning - // the dep in the nearest "deps" - // directory - var segs = parent.split('/'); - var i = lastIndexOf(segs, 'deps') + 1; - if (!i) i = 0; - path = segs.slice(0, i + 1).join('/') + '/deps/' + path; - return path; +require.define = function (name, exports) { + require.modules[name] = { + exports: exports }; - - /** - * Check if module is defined at `path`. - */ - - localRequire.exists = function(path) { - return require.modules.hasOwnProperty(localRequire.resolve(path)); - }; - - return localRequire; }; -require.register("component-emitter/index.js", function(exports, require, module){ +require.register("component~emitter@1.1.2", function (exports, module) { /** * Expose `Emitter`. @@ -238,7 +97,8 @@ function mixin(obj) { * @api public */ -Emitter.prototype.on = function(event, fn){ +Emitter.prototype.on = +Emitter.prototype.addEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; (this._callbacks[event] = this._callbacks[event] || []) .push(fn); @@ -264,7 +124,7 @@ Emitter.prototype.once = function(event, fn){ fn.apply(this, arguments); } - fn._off = on; + on.fn = fn; this.on(event, on); return this; }; @@ -281,8 +141,17 @@ Emitter.prototype.once = function(event, fn){ Emitter.prototype.off = Emitter.prototype.removeListener = -Emitter.prototype.removeAllListeners = function(event, fn){ +Emitter.prototype.removeAllListeners = +Emitter.prototype.removeEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event var callbacks = this._callbacks[event]; if (!callbacks) return this; @@ -293,8 +162,14 @@ Emitter.prototype.removeAllListeners = function(event, fn){ } // remove specific handler - var i = callbacks.indexOf(fn._off || fn); - if (~i) callbacks.splice(i, 1); + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } return this; }; @@ -347,50 +222,52 @@ Emitter.prototype.hasListeners = function(event){ }; }); -require.register("dropzone/index.js", function(exports, require, module){ + +require.register("dropzone", function (exports, module) { /** * Exposing dropzone */ -module.exports = require("./lib/dropzone.js"); +module.exports = require("dropzone/lib/dropzone.js"); }); -require.register("dropzone/lib/dropzone.js", function(exports, require, module){ -/* -# -# More info at [www.dropzonejs.com](http://www.dropzonejs.com) -# -# Copyright (c) 2012, Matias Meno -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -*/ +require.register("dropzone/lib/dropzone.js", function (exports, module) { + +/* + * + * More info at [www.dropzonejs.com](http://www.dropzonejs.com) + * + * Copyright (c) 2012, Matias Meno + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ (function() { - var Dropzone, Em, camelize, contentLoaded, noop, without, + var Dropzone, Em, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __slice = [].slice; - Em = typeof Emitter !== "undefined" && Emitter !== null ? Emitter : require("emitter"); + Em = typeof Emitter !== "undefined" && Emitter !== null ? Emitter : require("component~emitter@1.1.2"); noop = function() {}; @@ -399,16 +276,16 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ __extends(Dropzone, _super); + /* This is a list of all available events you can register on a dropzone object. You can register an event handler like this: dropzone.on("dragEnter", function() { }); - */ + */ - - Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "selectedfiles", "addedfile", "removedfile", "thumbnail", "error", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset"]; + Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached"]; Dropzone.prototype.defaultOptions = { url: null, @@ -422,6 +299,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ maxThumbnailFilesize: 10, thumbnailWidth: 100, thumbnailHeight: 100, + maxFiles: null, params: {}, clickable: true, ignoreHiddenFiles: true, @@ -433,12 +311,14 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ dictDefaultMessage: "Drop files here to upload", dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.", - dictFileTooBig: "File is too big ({{filesize}}MB). Max filesize: {{maxFilesize}}MB.", + dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.", dictInvalidFileType: "You can't upload files of this type.", dictResponseError: "Server responded with {{statusCode}} code.", dictCancelUpload: "Cancel upload", dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", dictRemoveFile: "Remove file", + dictRemoveFileConfirmation: null, + dictMaxFilesExceeded: "You can not upload any more files.", accept: function(file, done) { return done(); }, @@ -494,6 +374,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ info.srcY = (file.height - info.srcHeight) / 2; return info; }, + /* Those functions register themselves to the events on init and handle all the user interface specific stuff. Overwriting them won't break the upload @@ -501,8 +382,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ You can overwrite them if you don't like the default behavior. If you just want to add an additional event handler, register it on the dropzone object and don't overwrite those options. - */ - + */ drop: function(e) { return this.element.classList.remove("dz-drag-hover"); }, @@ -519,53 +399,94 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ dragleave: function(e) { return this.element.classList.remove("dz-drag-hover"); }, - selectedfiles: function(files) { - if (this.element === this.previewsContainer) { - return this.element.classList.add("dz-started"); - } - }, + paste: noop, reset: function() { return this.element.classList.remove("dz-started"); }, addedfile: function(file) { - var _this = this; - file.previewElement = Dropzone.createElement(this.options.previewTemplate); + var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; + if (this.element === this.previewsContainer) { + this.element.classList.add("dz-started"); + } + file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); file.previewTemplate = file.previewElement; this.previewsContainer.appendChild(file.previewElement); - file.previewElement.querySelector("[data-dz-name]").textContent = file.name; - file.previewElement.querySelector("[data-dz-size]").innerHTML = this.filesize(file.size); + _ref = file.previewElement.querySelectorAll("[data-dz-name]"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + node.textContent = file.name; + } + _ref1 = file.previewElement.querySelectorAll("[data-dz-size]"); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + node = _ref1[_j]; + node.innerHTML = this.filesize(file.size); + } if (this.options.addRemoveLinks) { - file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); - file._removeLink.addEventListener("click", function(e) { + file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); + file.previewElement.appendChild(file._removeLink); + } + removeFileEvent = (function(_this) { + return function(e) { e.preventDefault(); e.stopPropagation(); if (file.status === Dropzone.UPLOADING) { - if (window.confirm(_this.options.dictCancelUploadConfirmation)) { + return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() { + return _this.removeFile(file); + }); + } else { + if (_this.options.dictRemoveFileConfirmation) { + return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() { + return _this.removeFile(file); + }); + } else { return _this.removeFile(file); } - } else { - return _this.removeFile(file); } - }); - return file.previewElement.appendChild(file._removeLink); + }; + })(this); + _ref2 = file.previewElement.querySelectorAll("[data-dz-remove]"); + _results = []; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + removeLink = _ref2[_k]; + _results.push(removeLink.addEventListener("click", removeFileEvent)); } + return _results; }, removedfile: function(file) { var _ref; - return (_ref = file.previewElement) != null ? _ref.parentNode.removeChild(file.previewElement) : void 0; + if ((_ref = file.previewElement) != null) { + _ref.parentNode.removeChild(file.previewElement); + } + return this._updateMaxFilesReachedClass(); }, thumbnail: function(file, dataUrl) { - var thumbnailElement; + var thumbnailElement, _i, _len, _ref, _results; file.previewElement.classList.remove("dz-file-preview"); file.previewElement.classList.add("dz-image-preview"); - thumbnailElement = file.previewElement.querySelector("[data-dz-thumbnail]"); - thumbnailElement.alt = file.name; - return thumbnailElement.src = dataUrl; + _ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + thumbnailElement = _ref[_i]; + thumbnailElement.alt = file.name; + _results.push(thumbnailElement.src = dataUrl); + } + return _results; }, error: function(file, message) { + var node, _i, _len, _ref, _results; file.previewElement.classList.add("dz-error"); - return file.previewElement.querySelector("[data-dz-errormessage]").textContent = message; + if (typeof message !== "String" && message.error) { + message = message.error; + } + _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + _results.push(node.textContent = message); + } + return _results; }, + errormultiple: noop, processing: function(file) { file.previewElement.classList.add("dz-processing"); if (file._removeLink) { @@ -574,7 +495,14 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ }, processingmultiple: noop, uploadprogress: function(file, progress, bytesSent) { - return file.previewElement.querySelector("[data-dz-uploadprogress]").style.width = "" + progress + "%"; + var node, _i, _len, _ref, _results; + _ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]"); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + _results.push(node.style.width = "" + progress + "%"); + } + return _results; }, totaluploadprogress: noop, sending: noop, @@ -593,6 +521,8 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ } }, completemultiple: noop, + maxfilesexceeded: noop, + maxfilesreached: noop, previewTemplate: "
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
" }; @@ -627,11 +557,14 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ throw new Error("Dropzone already attached."); } Dropzone.instances.push(this); - element.dropzone = this; + this.element.dropzone = this; elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {}; this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); + if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { + return this.options.fallback.call(this); + } if (this.options.url == null) { - this.options.url = this.element.action; + this.options.url = this.element.getAttribute("action"); } if (!this.options.url) { throw new Error("No URL provided."); @@ -644,9 +577,6 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ delete this.options.acceptedMimeTypes; } this.options.method = this.options.method.toUpperCase(); - if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { - return this.options.fallback.call(this); - } if ((fallback = this.getExistingFallback()) && fallback.parentNode) { fallback.parentNode.removeChild(fallback); } @@ -718,8 +648,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ }; Dropzone.prototype.init = function() { - var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1, - _this = this; + var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1; if (this.element.tagName === "form") { this.element.setAttribute("enctype", "multipart/form-data"); } @@ -727,35 +656,40 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ this.element.appendChild(Dropzone.createElement("
" + this.options.dictDefaultMessage + "
")); } if (this.clickableElements.length) { - setupHiddenFileInput = function() { - if (_this.hiddenFileInput) { - document.body.removeChild(_this.hiddenFileInput); - } - _this.hiddenFileInput = document.createElement("input"); - _this.hiddenFileInput.setAttribute("type", "file"); - if (_this.options.uploadMultiple) { - _this.hiddenFileInput.setAttribute("multiple", "multiple"); - } - if (_this.options.acceptedFiles != null) { - _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles); - } - _this.hiddenFileInput.style.visibility = "hidden"; - _this.hiddenFileInput.style.position = "absolute"; - _this.hiddenFileInput.style.top = "0"; - _this.hiddenFileInput.style.left = "0"; - _this.hiddenFileInput.style.height = "0"; - _this.hiddenFileInput.style.width = "0"; - document.body.appendChild(_this.hiddenFileInput); - return _this.hiddenFileInput.addEventListener("change", function() { - var files; - files = _this.hiddenFileInput.files; - if (files.length) { - _this.emit("selectedfiles", files); - _this.handleFiles(files); + setupHiddenFileInput = (function(_this) { + return function() { + if (_this.hiddenFileInput) { + document.body.removeChild(_this.hiddenFileInput); } - return setupHiddenFileInput(); - }); - }; + _this.hiddenFileInput = document.createElement("input"); + _this.hiddenFileInput.setAttribute("type", "file"); + if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) { + _this.hiddenFileInput.setAttribute("multiple", "multiple"); + } + _this.hiddenFileInput.className = "dz-hidden-input"; + if (_this.options.acceptedFiles != null) { + _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles); + } + _this.hiddenFileInput.style.visibility = "hidden"; + _this.hiddenFileInput.style.position = "absolute"; + _this.hiddenFileInput.style.top = "0"; + _this.hiddenFileInput.style.left = "0"; + _this.hiddenFileInput.style.height = "0"; + _this.hiddenFileInput.style.width = "0"; + document.body.appendChild(_this.hiddenFileInput); + return _this.hiddenFileInput.addEventListener("change", function() { + var file, files, _i, _len; + files = _this.hiddenFileInput.files; + if (files.length) { + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _this.addFile(file); + } + } + return setupHiddenFileInput(); + }); + }; + })(this); setupHiddenFileInput(); } this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL; @@ -764,15 +698,30 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ eventName = _ref1[_i]; this.on(eventName, this.options[eventName]); } - this.on("uploadprogress", function() { - return _this.updateTotalUploadProgress(); - }); - this.on("removedfile", function() { - return _this.updateTotalUploadProgress(); - }); - this.on("canceled", function(file) { - return _this.emit("complete", file); - }); + this.on("uploadprogress", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("removedfile", (function(_this) { + return function() { + return _this.updateTotalUploadProgress(); + }; + })(this)); + this.on("canceled", (function(_this) { + return function(file) { + return _this.emit("complete", file); + }; + })(this)); + this.on("complete", (function(_this) { + return function(file) { + if (_this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) { + return setTimeout((function() { + return _this.emit("queuecomplete"); + }), 0); + } + }; + })(this)); noPropagation = function(e) { e.stopPropagation(); if (e.preventDefault) { @@ -785,43 +734,61 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ { element: this.element, events: { - "dragstart": function(e) { - return _this.emit("dragstart", e); - }, - "dragenter": function(e) { - noPropagation(e); - return _this.emit("dragenter", e); - }, - "dragover": function(e) { - noPropagation(e); - return _this.emit("dragover", e); - }, - "dragleave": function(e) { - return _this.emit("dragleave", e); - }, - "drop": function(e) { - noPropagation(e); - _this.drop(e); - return _this.emit("drop", e); - }, - "dragend": function(e) { - return _this.emit("dragend", e); - } + "dragstart": (function(_this) { + return function(e) { + return _this.emit("dragstart", e); + }; + })(this), + "dragenter": (function(_this) { + return function(e) { + noPropagation(e); + return _this.emit("dragenter", e); + }; + })(this), + "dragover": (function(_this) { + return function(e) { + var efct; + try { + efct = e.dataTransfer.effectAllowed; + } catch (_error) {} + e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy'; + noPropagation(e); + return _this.emit("dragover", e); + }; + })(this), + "dragleave": (function(_this) { + return function(e) { + return _this.emit("dragleave", e); + }; + })(this), + "drop": (function(_this) { + return function(e) { + noPropagation(e); + return _this.drop(e); + }; + })(this), + "dragend": (function(_this) { + return function(e) { + return _this.emit("dragend", e); + }; + })(this) } } ]; - this.clickableElements.forEach(function(clickableElement) { - return _this.listeners.push({ - element: clickableElement, - events: { - "click": function(evt) { - if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) { - return _this.hiddenFileInput.click(); + this.clickableElements.forEach((function(_this) { + return function(clickableElement) { + return _this.listeners.push({ + element: clickableElement, + events: { + "click": function(evt) { + if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) { + return _this.hiddenFileInput.click(); + } } } - } - }); - }); + }); + }; + })(this)); this.enable(); return this.options.init.call(this); }; @@ -834,7 +801,8 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); this.hiddenFileInput = null; } - return delete this.element.dropzone; + delete this.element.dropzone; + return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); }; Dropzone.prototype.updateTotalUploadProgress = function() { @@ -865,7 +833,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ if (this.options.dictFallbackText) { fieldsString += "

" + this.options.dictFallbackText + "

"; } - fieldsString += ""; + fieldsString += ""; fields = Dropzone.createElement(fieldsString); if (this.element.tagName !== "FORM") { form = Dropzone.createElement(""); @@ -961,18 +929,18 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ Dropzone.prototype.filesize = function(size) { var string; - if (size >= 100000000000) { - size = size / 100000000000; - string = "TB"; - } else if (size >= 100000000) { - size = size / 100000000; - string = "GB"; - } else if (size >= 100000) { - size = size / 100000; - string = "MB"; - } else if (size >= 100) { - size = size / 100; - string = "KB"; + if (size >= 1024 * 1024 * 1024 * 1024 / 10) { + size = size / (1024 * 1024 * 1024 * 1024 / 10); + string = "TiB"; + } else if (size >= 1024 * 1024 * 1024 / 10) { + size = size / (1024 * 1024 * 1024 / 10); + string = "GiB"; + } else if (size >= 1024 * 1024 / 10) { + size = size / (1024 * 1024 / 10); + string = "MiB"; + } else if (size >= 1024 / 10) { + size = size / (1024 / 10); + string = "KiB"; } else { size = size * 10; string = "b"; @@ -980,23 +948,46 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ return "" + (Math.round(size) / 10) + " " + string; }; + Dropzone.prototype._updateMaxFilesReachedClass = function() { + if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + if (this.getAcceptedFiles().length === this.options.maxFiles) { + this.emit('maxfilesreached', this.files); + } + return this.element.classList.add("dz-max-files-reached"); + } else { + return this.element.classList.remove("dz-max-files-reached"); + } + }; + Dropzone.prototype.drop = function(e) { var files, items; if (!e.dataTransfer) { return; } + this.emit("drop", e); files = e.dataTransfer.files; - this.emit("selectedfiles", files); if (files.length) { items = e.dataTransfer.items; - if (items && items.length && ((items[0].webkitGetAsEntry != null) || (items[0].getAsEntry != null))) { - this.handleItems(items); + if (items && items.length && (items[0].webkitGetAsEntry != null)) { + this._addFilesFromItems(items); } else { this.handleFiles(files); } } }; + Dropzone.prototype.paste = function(e) { + var items, _ref; + if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) { + return; + } + this.emit("paste", e); + items = e.clipboardData.items; + if (items.length) { + return this._addFilesFromItems(items); + } + }; + Dropzone.prototype.handleFiles = function(files) { var file, _i, _len, _results; _results = []; @@ -1007,21 +998,57 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ return _results; }; - Dropzone.prototype.handleItems = function(items) { - var entry, item, _i, _len; + Dropzone.prototype._addFilesFromItems = function(items) { + var entry, item, _i, _len, _results; + _results = []; for (_i = 0, _len = items.length; _i < _len; _i++) { item = items[_i]; - if (item.webkitGetAsEntry != null) { - entry = item.webkitGetAsEntry(); + if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { if (entry.isFile) { - this.addFile(item.getAsFile()); + _results.push(this.addFile(item.getAsFile())); } else if (entry.isDirectory) { - this.addDirectory(entry, entry.name); + _results.push(this._addFilesFromDirectory(entry, entry.name)); + } else { + _results.push(void 0); + } + } else if (item.getAsFile != null) { + if ((item.kind == null) || item.kind === "file") { + _results.push(this.addFile(item.getAsFile())); + } else { + _results.push(void 0); } } else { - this.addFile(item.getAsFile()); + _results.push(void 0); } } + return _results; + }; + + Dropzone.prototype._addFilesFromDirectory = function(directory, path) { + var dirReader, entriesReader; + dirReader = directory.createReader(); + entriesReader = (function(_this) { + return function(entries) { + var entry, _i, _len; + for (_i = 0, _len = entries.length; _i < _len; _i++) { + entry = entries[_i]; + if (entry.isFile) { + entry.file(function(file) { + if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { + return; + } + file.fullPath = "" + path + "/" + file.name; + return _this.addFile(file); + }); + } else if (entry.isDirectory) { + _this._addFilesFromDirectory(entry, "" + path + "/" + entry.name); + } + } + }; + })(this); + return dirReader.readEntries(entriesReader, function(error) { + return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0; + }); }; Dropzone.prototype.accept = function(file, done) { @@ -1029,13 +1056,15 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { return done(this.options.dictInvalidFileType); + } else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { + done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles)); + return this.emit("maxfilesexceeded", file); } else { return this.options.accept.call(this, file, done); } }; Dropzone.prototype.addFile = function(file) { - var _this = this; file.upload = { progress: 0, total: file.size, @@ -1044,18 +1073,18 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ this.files.push(file); file.status = Dropzone.ADDED; this.emit("addedfile", file); - if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { - this.createThumbnail(file); - } - return this.accept(file, function(error) { - if (error) { - file.accepted = false; - return _this._errorProcessing([file], error); - } else { - file.accepted = true; - return _this.enqueueFile(file); - } - }); + this._enqueueThumbnail(file); + return this.accept(file, (function(_this) { + return function(error) { + if (error) { + file.accepted = false; + _this._errorProcessing([file], error); + } else { + _this.enqueueFile(file); + } + return _this._updateMaxFilesReachedClass(); + }; + })(this)); }; Dropzone.prototype.enqueueFiles = function(files) { @@ -1068,43 +1097,47 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ }; Dropzone.prototype.enqueueFile = function(file) { - var _this = this; + file.accepted = true; if (file.status === Dropzone.ADDED) { file.status = Dropzone.QUEUED; if (this.options.autoProcessQueue) { - return setTimeout((function() { - return _this.processQueue(); - }), 1); + return setTimeout(((function(_this) { + return function() { + return _this.processQueue(); + }; + })(this)), 0); } } else { throw new Error("This file can't be queued because it has already been processed or was rejected."); } }; - Dropzone.prototype.addDirectory = function(entry, path) { - var dirReader, entriesReader, - _this = this; - dirReader = entry.createReader(); - entriesReader = function(entries) { - var _i, _len; - for (_i = 0, _len = entries.length; _i < _len; _i++) { - entry = entries[_i]; - if (entry.isFile) { - entry.file(function(file) { - if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { - return; - } - file.fullPath = "" + path + "/" + file.name; - return _this.addFile(file); - }); - } else if (entry.isDirectory) { - _this.addDirectory(entry, "" + path + "/" + entry.name); - } - } - }; - return dirReader.readEntries(entriesReader, function(error) { - return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0; - }); + Dropzone.prototype._thumbnailQueue = []; + + Dropzone.prototype._processingThumbnail = false; + + Dropzone.prototype._enqueueThumbnail = function(file) { + if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { + this._thumbnailQueue.push(file); + return setTimeout(((function(_this) { + return function() { + return _this._processThumbnailQueue(); + }; + })(this)), 0); + } + }; + + Dropzone.prototype._processThumbnailQueue = function() { + if (this._processingThumbnail || this._thumbnailQueue.length === 0) { + return; + } + this._processingThumbnail = true; + return this.createThumbnail(this._thumbnailQueue.shift(), (function(_this) { + return function() { + _this._processingThumbnail = false; + return _this._processThumbnailQueue(); + }; + })(this)); }; Dropzone.prototype.removeFile = function(file) { @@ -1133,34 +1166,38 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ return null; }; - Dropzone.prototype.createThumbnail = function(file) { - var fileReader, - _this = this; + Dropzone.prototype.createThumbnail = function(file, callback) { + var fileReader; fileReader = new FileReader; - fileReader.onload = function() { - var img; - img = new Image; - img.onload = function() { - var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3; - file.width = img.width; - file.height = img.height; - resizeInfo = _this.options.resize.call(_this, file); - if (resizeInfo.trgWidth == null) { - resizeInfo.trgWidth = _this.options.thumbnailWidth; - } - if (resizeInfo.trgHeight == null) { - resizeInfo.trgHeight = _this.options.thumbnailHeight; - } - canvas = document.createElement("canvas"); - ctx = canvas.getContext("2d"); - canvas.width = resizeInfo.trgWidth; - canvas.height = resizeInfo.trgHeight; - ctx.drawImage(img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); - thumbnail = canvas.toDataURL("image/png"); - return _this.emit("thumbnail", file, thumbnail); + fileReader.onload = (function(_this) { + return function() { + var img; + img = document.createElement("img"); + img.onload = function() { + var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3; + file.width = img.width; + file.height = img.height; + resizeInfo = _this.options.resize.call(_this, file); + if (resizeInfo.trgWidth == null) { + resizeInfo.trgWidth = _this.options.thumbnailWidth; + } + if (resizeInfo.trgHeight == null) { + resizeInfo.trgHeight = _this.options.thumbnailHeight; + } + canvas = document.createElement("canvas"); + ctx = canvas.getContext("2d"); + canvas.width = resizeInfo.trgWidth; + canvas.height = resizeInfo.trgHeight; + drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); + thumbnail = canvas.toDataURL("image/png"); + _this.emit("thumbnail", file, thumbnail); + if (callback != null) { + return callback(); + } + }; + return img.src = fileReader.result; }; - return img.src = fileReader.result; - }; + })(this); return fileReader.readAsDataURL(file); }; @@ -1169,12 +1206,15 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ parallelUploads = this.options.parallelUploads; processingLength = this.getUploadingFiles().length; i = processingLength; + if (processingLength >= parallelUploads) { + return; + } queuedFiles = this.getQueuedFiles(); if (!(queuedFiles.length > 0)) { return; } if (this.options.uploadMultiple) { - return this.processFiles(queuedFiles.slice(0, parallelUploads)); + return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength)); } else { while (i < parallelUploads) { if (!queuedFiles.length) { @@ -1253,8 +1293,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ }; Dropzone.prototype.uploadFiles = function(files) { - var file, formData, handleError, header, headers, input, inputName, inputType, key, name, progressObj, response, updateProgress, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, - _this = this; + var file, formData, handleError, headerName, headerValue, headers, input, inputName, inputType, key, option, progressObj, response, updateProgress, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3, _ref4; xhr = new XMLHttpRequest(); for (_i = 0, _len = files.length; _i < _len; _i++) { file = files[_i]; @@ -1263,79 +1302,87 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ xhr.open(this.options.method, this.options.url, true); xhr.withCredentials = !!this.options.withCredentials; response = null; - handleError = function() { - var _j, _len1, _results; - _results = []; - for (_j = 0, _len1 = files.length; _j < _len1; _j++) { - file = files[_j]; - _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr)); - } - return _results; - }; - updateProgress = function(e) { - var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results; - if (e != null) { - progress = 100 * e.loaded / e.total; + handleError = (function(_this) { + return function() { + var _j, _len1, _results; + _results = []; for (_j = 0, _len1 = files.length; _j < _len1; _j++) { file = files[_j]; - file.upload = { - progress: progress, - total: e.total, - bytesSent: e.loaded - }; + _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr)); } - } else { - allFilesFinished = true; - progress = 100; - for (_k = 0, _len2 = files.length; _k < _len2; _k++) { - file = files[_k]; - if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) { - allFilesFinished = false; + return _results; + }; + })(this); + updateProgress = (function(_this) { + return function(e) { + var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results; + if (e != null) { + progress = 100 * e.loaded / e.total; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + file.upload = { + progress: progress, + total: e.total, + bytesSent: e.loaded + }; + } + } else { + allFilesFinished = true; + progress = 100; + for (_k = 0, _len2 = files.length; _k < _len2; _k++) { + file = files[_k]; + if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) { + allFilesFinished = false; + } + file.upload.progress = progress; + file.upload.bytesSent = file.upload.total; + } + if (allFilesFinished) { + return; } - file.upload.progress = progress; - file.upload.bytesSent = file.upload.total; } - if (allFilesFinished) { + _results = []; + for (_l = 0, _len3 = files.length; _l < _len3; _l++) { + file = files[_l]; + _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent)); + } + return _results; + }; + })(this); + xhr.onload = (function(_this) { + return function(e) { + var _ref; + if (files[0].status === Dropzone.CANCELED) { return; } - } - _results = []; - for (_l = 0, _len3 = files.length; _l < _len3; _l++) { - file = files[_l]; - _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent)); - } - return _results; - }; - xhr.onload = function(e) { - var _ref; - if (files[0].status === Dropzone.CANCELED) { - return; - } - if (xhr.readyState !== 4) { - return; - } - response = xhr.responseText; - if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { - try { - response = JSON.parse(response); - } catch (_error) { - e = _error; - response = "Invalid JSON response from server."; + if (xhr.readyState !== 4) { + return; + } + response = xhr.responseText; + if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { + try { + response = JSON.parse(response); + } catch (_error) { + e = _error; + response = "Invalid JSON response from server."; + } + } + updateProgress(); + if (!((200 <= (_ref = xhr.status) && _ref < 300))) { + return handleError(); + } else { + return _this._finished(files, response, e); + } + }; + })(this); + xhr.onerror = (function(_this) { + return function() { + if (files[0].status === Dropzone.CANCELED) { + return; } - } - updateProgress(); - if (!((200 <= (_ref = xhr.status) && _ref < 300))) { return handleError(); - } else { - return _this._finished(files, response, e); - } - }; - xhr.onerror = function() { - if (files[0].status === Dropzone.CANCELED) { - return; - } - return handleError(); - }; + }; + })(this); progressObj = (_ref = xhr.upload) != null ? _ref : xhr; progressObj.onprogress = updateProgress; headers = { @@ -1346,9 +1393,9 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ if (this.options.headers) { extend(headers, this.options.headers); } - for (header in headers) { - name = headers[header]; - xhr.setRequestHeader(header, name); + for (headerName in headers) { + headerValue = headers[headerName]; + xhr.setRequestHeader(headerName, headerValue); } formData = new FormData(); if (this.options.params) { @@ -1371,13 +1418,21 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ input = _ref2[_k]; inputName = input.getAttribute("name"); inputType = input.getAttribute("type"); - if (!inputType || ((_ref3 = inputType.toLowerCase()) !== "checkbox" && _ref3 !== "radio") || input.checked) { + if (input.tagName === "SELECT" && input.hasAttribute("multiple")) { + _ref3 = input.options; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + option = _ref3[_l]; + if (option.selected) { + formData.append(inputName, option.value); + } + } + } else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) { formData.append(inputName, input.value); } } } - for (_l = 0, _len3 = files.length; _l < _len3; _l++) { - file = files[_l]; + for (_m = 0, _len4 = files.length; _m < _len4; _m++) { + file = files[_m]; formData.append("" + this.options.paramName + (this.options.uploadMultiple ? "[]" : ""), file, file.name); } return xhr.send(formData); @@ -1421,13 +1476,13 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ })(Em); - Dropzone.version = "3.6.1"; + Dropzone.version = "3.8.5"; Dropzone.options = {}; Dropzone.optionsForElement = function(element) { - if (element.id) { - return Dropzone.options[camelize(element.id)]; + if (element.getAttribute("id")) { + return Dropzone.options[camelize(element.getAttribute("id"))]; } else { return void 0; } @@ -1449,9 +1504,6 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ Dropzone.discover = function() { var checkElements, dropzone, dropzones, _i, _len, _results; - if (!Dropzone.autoDiscover) { - return; - } if (document.querySelectorAll) { dropzones = document.querySelectorAll(".dropzone"); } else { @@ -1522,7 +1574,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ camelize = function(str) { return str.replace(/[\-_](\w)/g, function(match) { - return match[1].toUpperCase(); + return match.charAt(1).toUpperCase(); }); }; @@ -1587,6 +1639,14 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ return elements; }; + Dropzone.confirm = function(question, accepted, rejected) { + if (window.confirm(question)) { + return accepted(); + } else if (rejected != null) { + return rejected(); + } + }; + Dropzone.isValidFile = function(file, acceptedFiles) { var baseMimeType, mimeType, validType, _i, _len; if (!acceptedFiles) { @@ -1599,7 +1659,7 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ validType = acceptedFiles[_i]; validType = validType.trim(); if (validType.charAt(0) === ".") { - if (file.name.indexOf(validType, file.name.length - validType.length) !== -1) { + if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { return true; } } else if (/\/\*$/.test(validType)) { @@ -1645,20 +1705,64 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ Dropzone.SUCCESS = "success"; - /* - # contentloaded.js - # - # Author: Diego Perini (diego.perini at gmail.com) - # Summary: cross-browser wrapper for DOMContentLoaded - # Updated: 20101020 - # License: MIT - # Version: 1.2 - # - # URL: - # http://javascript.nwbox.com/ContentLoaded/ - # http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE - */ + /* + + Bugfix for iOS 6 and 7 + Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios + based on the work of https://github.com/stomita/ios-imagefile-megapixel + */ + + detectVerticalSquash = function(img) { + var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy; + iw = img.naturalWidth; + ih = img.naturalHeight; + canvas = document.createElement("canvas"); + canvas.width = 1; + canvas.height = ih; + ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + data = ctx.getImageData(0, 0, 1, ih).data; + sy = 0; + ey = ih; + py = ih; + while (py > sy) { + alpha = data[(py - 1) * 4 + 3]; + if (alpha === 0) { + ey = py; + } else { + sy = py; + } + py = (ey + sy) >> 1; + } + ratio = py / ih; + if (ratio === 0) { + return 1; + } else { + return ratio; + } + }; + + drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { + var vertSquashRatio; + vertSquashRatio = detectVerticalSquash(img); + return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); + }; + + + /* + * contentloaded.js + * + * Author: Diego Perini (diego.perini at gmail.com) + * Summary: cross-browser wrapper for DOMContentLoaded + * Updated: 20101020 + * License: MIT + * Version: 1.2 + * + * URL: + * http://javascript.nwbox.com/ContentLoaded/ + * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE + */ contentLoaded = function(win, fn) { var add, doc, done, init, poll, pre, rem, root, top; @@ -1704,18 +1808,23 @@ require.register("dropzone/lib/dropzone.js", function(exports, require, module){ } }; - contentLoaded(window, Dropzone.discover); + Dropzone._autoDiscoverFunction = function() { + if (Dropzone.autoDiscover) { + return Dropzone.discover(); + } + }; + + contentLoaded(window, Dropzone._autoDiscoverFunction); }).call(this); }); -require.alias("component-emitter/index.js", "dropzone/deps/emitter/index.js"); -require.alias("component-emitter/index.js", "emitter/index.js"); if (typeof exports == "object") { module.exports = require("dropzone"); } else if (typeof define == "function" && define.amd) { - define(function(){ return require("dropzone"); }); + define([], function(){ return require("dropzone"); }); } else { this["Dropzone"] = require("dropzone"); -}})(); \ No newline at end of file +} +})() From 1f66670819d8d810005329215152d276a319f2e6 Mon Sep 17 00:00:00 2001 From: shimamoto Date: Sun, 27 Apr 2014 16:59:54 +0900 Subject: [PATCH 56/76] (refs #12) Implemented the upload area in preview.hrml. --- src/main/scala/app/FileUploadController.scala | 2 ++ src/main/twirl/helper/preview.scala.html | 17 ++++++++++++++++- src/main/twirl/helper/uploadavatar.scala.html | 1 - 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/scala/app/FileUploadController.scala b/src/main/scala/app/FileUploadController.scala index 970da6ec1..4b625a256 100644 --- a/src/main/scala/app/FileUploadController.scala +++ b/src/main/scala/app/FileUploadController.scala @@ -24,7 +24,9 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport { } post("/image/:owner/:repository"){ + execute { (file, fileId) => + } } private def execute(f: (FileItem, String) => Unit) = fileParams.get("file") match { diff --git a/src/main/twirl/helper/preview.scala.html b/src/main/twirl/helper/preview.scala.html index cc6504bdc..1cba06abc 100644 --- a/src/main/twirl/helper/preview.scala.html +++ b/src/main/twirl/helper/preview.scala.html @@ -15,7 +15,10 @@
- +
+ + Attach images by dragging & dropping, or selecting them. +
@@ -42,5 +45,17 @@ $(function(){ prettyPrint(); }); }); + + @if(!enableWikiLink){ + $("div#clickable").dropzone({ + url: '@path/upload/image/@repository.owner/@repository.name', + maxFilesize: 10, + previewTemplate: "
\n
Uploading your images…
\n
\n
", + success: function(file, id) { + $('#content').val($('#content').val() + "\n" + id + "\n" + file.name); + file.previewElement.remove(); + } + }); + } }); diff --git a/src/main/twirl/helper/uploadavatar.scala.html b/src/main/twirl/helper/uploadavatar.scala.html index f1a58f3cd..5de7ae47a 100644 --- a/src/main/twirl/helper/uploadavatar.scala.html +++ b/src/main/twirl/helper/uploadavatar.scala.html @@ -21,7 +21,6 @@ $(function(){ var dropzone = new Dropzone('div#clickable', { url: '@path/upload/image', previewsContainer: 'div#avatar', - paramName: 'file', parallelUploads: 1, thumbnailWidth: 120, thumbnailHeight: 120 From 63c4e1225915b29b81229ee8a929e7b03ac96657 Mon Sep 17 00:00:00 2001 From: shimamoto Date: Sun, 27 Apr 2014 19:11:14 +0900 Subject: [PATCH 57/76] (refs #12) Change the Markdown notation of images. --- src/main/scala/app/IssuesController.scala | 5 +++++ src/main/twirl/helper/preview.scala.html | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index be564ee39..79ba33cd0 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -273,6 +273,11 @@ trait IssuesControllerBase extends ControllerBase { } }) + get("/:owner/:repository/_attached/:file")(referrersOnly { repository => + println("----" + params("file")) + + }) + val assignedUserName = (key: String) => params.get(key) filter (_.trim != "") val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt) diff --git a/src/main/twirl/helper/preview.scala.html b/src/main/twirl/helper/preview.scala.html index 1cba06abc..478a4084e 100644 --- a/src/main/twirl/helper/preview.scala.html +++ b/src/main/twirl/helper/preview.scala.html @@ -50,9 +50,11 @@ $(function(){ $("div#clickable").dropzone({ url: '@path/upload/image/@repository.owner/@repository.name', maxFilesize: 10, - previewTemplate: "
\n
Uploading your images…
\n
\n
", + previewTemplate: "
\n
Uploading your images...
\n
\n
", success: function(file, id) { - $('#content').val($('#content').val() + "\n" + id + "\n" + file.name); + var index = file.name.indexOf('.'); + $('#content').val($('#content').val() + + '\n![' + file.name.substring(0, index) + '](@url(repository)/_attached/' + id + file.name.substring(index) + ')'); file.previewElement.remove(); } }); From 27fa9df2ee15364f63845fe8e7c46203001e40ea Mon Sep 17 00:00:00 2001 From: shimamoto Date: Sun, 27 Apr 2014 21:08:31 +0900 Subject: [PATCH 58/76] (refs #12) Implemented the process of saving image. --- src/main/scala/app/FileUploadController.scala | 2 +- src/main/scala/app/IssuesController.scala | 3 +-- src/main/twirl/helper/preview.scala.html | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/scala/app/FileUploadController.scala b/src/main/scala/app/FileUploadController.scala index 4b625a256..ad8ea2829 100644 --- a/src/main/scala/app/FileUploadController.scala +++ b/src/main/scala/app/FileUploadController.scala @@ -25,7 +25,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport { post("/image/:owner/:repository"){ execute { (file, fileId) => - + FileUtils.writeByteArrayToFile(new java.io.File(getAttachedDir(params("owner"), params("repository")), fileId), file.get) } } diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index 79ba33cd0..a72fd78eb 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -274,8 +274,7 @@ trait IssuesControllerBase extends ControllerBase { }) get("/:owner/:repository/_attached/:file")(referrersOnly { repository => - println("----" + params("file")) - + new java.io.File(Directory.getAttachedDir(repository.owner, repository.name), params("file")) }) val assignedUserName = (key: String) => params.get(key) filter (_.trim != "") diff --git a/src/main/twirl/helper/preview.scala.html b/src/main/twirl/helper/preview.scala.html index 478a4084e..7a7c0718d 100644 --- a/src/main/twirl/helper/preview.scala.html +++ b/src/main/twirl/helper/preview.scala.html @@ -52,9 +52,8 @@ $(function(){ maxFilesize: 10, previewTemplate: "
\n
Uploading your images...
\n
\n
", success: function(file, id) { - var index = file.name.indexOf('.'); - $('#content').val($('#content').val() + - '\n![' + file.name.substring(0, index) + '](@url(repository)/_attached/' + id + file.name.substring(index) + ')'); + var images = '\n![' + file.name.split('.')[0] + '](@url(repository)/_attached/' + id + ')'; + $('#content').val($('#content').val() + images); file.previewElement.remove(); } }); From 7da2c650d2c5574166aa5405737190672a9c6165 Mon Sep 17 00:00:00 2001 From: takezoe Date: Mon, 28 Apr 2014 01:33:13 +0900 Subject: [PATCH 59/76] (refs #13)Bug fix --- .../app/RepositoryViewerController.scala | 6 ++-- src/main/twirl/repo/editor.scala.html | 36 +++++++++++++++++-- src/main/twirl/repo/files.scala.html | 8 +++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 5b3b3e219..036e1869e 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -356,7 +356,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { repo.html.files(revision, repository, if(path == ".") Nil else path.split("/").toList, // current path new JGitUtil.CommitInfo(revCommit), // latest commit - files, readme) + files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount)) } } getOrElse NotFound } @@ -379,7 +379,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { val headTip = git.getRepository.resolve(s"refs/heads/${branch}") JGitUtil.processTree(git, headTip){ (path, tree) => - if(path != newPath && !oldPath.exists(_ == path)){ + if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){ builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) } } @@ -387,8 +387,8 @@ trait RepositoryViewerControllerBase extends ControllerBase { newPath.foreach { newPath => builder.add(JGitUtil.createDirCacheEntry(newPath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset)))) - builder.finish() } + builder.finish() val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), loginAccount.fullName, loginAccount.mailAddress, message) diff --git a/src/main/twirl/repo/editor.scala.html b/src/main/twirl/repo/editor.scala.html index 2be0590fe..77d10269e 100644 --- a/src/main/twirl/repo/editor.scala.html +++ b/src/main/twirl/repo/editor.scala.html @@ -27,6 +27,21 @@ }
+ @* + + + + *@
+ + +
@@ -59,17 +74,34 @@ $(function(){ $('#editor').text($('#initial').val()); var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); + editor.getSession().setUseWrapMode(false); + @if(fileName.isDefined){ editor.getSession().setMode("ace/mode/@editorType(fileName.get)"); } + editor.on('change', function(){ $('#commit').attr('disabled', editor.getValue() == $('#initial').val()); }); + @* + $('#wrap').change(function(){ + console.log($('#wrap option:selected').val()); + if($('#wrap option:selected').val() == 'true'){ + editor.getSession().setUseWrapMode(true); + } else { + editor.getSession().setUseWrapMode(false); + } + }); + + $('#indent').change(function(){ + console.log($('#indent option:selected').val()); + editor.getSession().setUseWrapMode(parseInt($('#indent option:selected').val())); + }); + *@ + $('#commit').click(function(){ $('#content').val(editor.getValue()); }); - - }) diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 0077f8c5e..aef88a223 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -3,7 +3,8 @@ pathList: List[String], latestCommit: util.JGitUtil.CommitInfo, files: List[util.JGitUtil.FileInfo], - readme: Option[(List[String], String)])(implicit context: app.Context) + readme: Option[(List[String], String)], + hasWritePermission: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @@ -19,8 +20,9 @@ @pathList.zipWithIndex.map { case (section, i) => @section / } - - + + @if(hasWritePermission){ + + + }
From 1c24090c14d5ab27ab919a070d20f835709c1f4b Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 28 Apr 2014 10:39:32 +0900 Subject: [PATCH 60/76] (refs #13)Fix real-time validation for filename and Add line wrap mode switcher --- src/main/twirl/repo/blob.scala.html | 8 ++-- src/main/twirl/repo/editor.scala.html | 48 +++++++++---------- src/main/webapp/assets/common/js/gitbucket.js | 24 +++++++++- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/main/twirl/repo/blob.scala.html b/src/main/twirl/repo/blob.scala.html index b13846b49..06d8234b0 100644 --- a/src/main/twirl/repo/blob.scala.html +++ b/src/main/twirl/repo/blob.scala.html @@ -31,12 +31,12 @@
@if(hasWritePermission){ - Edit + Edit } - Raw - History + Raw + History @if(hasWritePermission){ - Delete + Delete }
diff --git a/src/main/twirl/repo/editor.scala.html b/src/main/twirl/repo/editor.scala.html index 77d10269e..f78b9beb6 100644 --- a/src/main/twirl/repo/editor.scala.html +++ b/src/main/twirl/repo/editor.scala.html @@ -20,31 +20,22 @@ -
- @* - *@
- - +
+ +
-
+
@@ -74,17 +65,24 @@ $(function(){ $('#editor').text($('#initial').val()); var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); - editor.getSession().setUseWrapMode(false); + //editor.getSession().setUseWrapMode(false); @if(fileName.isDefined){ editor.getSession().setMode("ace/mode/@editorType(fileName.get)"); } editor.on('change', function(){ - $('#commit').attr('disabled', editor.getValue() == $('#initial').val()); + updateCommitButtonStatus(); }); - @* + function updateCommitButtonStatus(){ + if(editor.getValue() == $('#initial').val() && $('#newFileName').val() == $('#oldFileName').val()){ + $('#commit').attr('disabled', true); + } else { + $('#commit').attr('disabled', false); + } + } + $('#wrap').change(function(){ console.log($('#wrap option:selected').val()); if($('#wrap option:selected').val() == 'true'){ @@ -94,14 +92,12 @@ $(function(){ } }); - $('#indent').change(function(){ - console.log($('#indent option:selected').val()); - editor.getSession().setUseWrapMode(parseInt($('#indent option:selected').val())); + $('#newFileName').watch(function(){ + updateCommitButtonStatus(); }); - *@ $('#commit').click(function(){ $('#content').val(editor.getValue()); }); -}) +}); diff --git a/src/main/webapp/assets/common/js/gitbucket.js b/src/main/webapp/assets/common/js/gitbucket.js index 26df1492e..02529ad0f 100644 --- a/src/main/webapp/assets/common/js/gitbucket.js +++ b/src/main/webapp/assets/common/js/gitbucket.js @@ -44,4 +44,26 @@ function displayErrors(data){ } i++; }); -} \ No newline at end of file +} + +(function($){ + $.fn.watch = function(callback){ + var timer = null; + var prevValue = this.val(); + + this.on('focus', function(e){ + window.clearInterval(timer); + timer = window.setInterval(function(){ + var newValue = $(e.target).val(); + if(prevValue != newValue){ + callback(); + } + prevValue = newValue; + }, 10); + }); + + this.on('blur', function(){ + window.clearInterval(timer); + }); + }; +})(jQuery); \ No newline at end of file From 560950799129d00512d6e626b7278cf6715a87c7 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 28 Apr 2014 10:45:13 +0900 Subject: [PATCH 61/76] (refs #13)Fix link target of cancel button --- src/main/twirl/repo/editor.scala.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/twirl/repo/editor.scala.html b/src/main/twirl/repo/editor.scala.html index f78b9beb6..b303ef5d0 100644 --- a/src/main/twirl/repo/editor.scala.html +++ b/src/main/twirl/repo/editor.scala.html @@ -49,7 +49,11 @@
- Cancel + @if(fileName.isEmpty){ + Cancel + } else { + Cancel + } From 779df30ec805924e7edc858367619c14031b36a3 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Apr 2014 02:03:28 +0900 Subject: [PATCH 62/76] Fix #355 --- src/main/scala/app/ControllerBase.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index 82ea872cc..1b2e0f905 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -139,10 +139,10 @@ abstract class ControllerBase extends ScalatraFilter */ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){ - lazy val path = settings.baseUrl.getOrElse(request.getServletContext.getContextPath) - lazy val currentPath = request.getRequestURI.substring(request.getContextPath.length) - lazy val baseURL = settings.baseUrl.getOrElse(request.getRequestURL.substring(0, request.getRequestURL.length - request.getRequestURI.length)) - lazy val host = new java.net.URL(baseURL).getHost + val path = settings.baseUrl.getOrElse(request.getServletContext.getContextPath) + val currentPath = request.getRequestURI.substring(request.getContextPath.length) + val baseURL = settings.baseUrl.getOrElse(request.getRequestURL.substring(0, request.getRequestURL.length - request.getRequestURI.length)) + val host = new java.net.URL(baseURL).getHost /** * Get object from cache. From 3cae337487b64c5800dfa9b124b06b2f1957cc08 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Apr 2014 02:41:19 +0900 Subject: [PATCH 63/76] (refs #13)New file icon --- src/main/twirl/repo/files.scala.html | 2 +- src/main/webapp/assets/common/images/newfile.png | Bin 0 -> 117 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/main/webapp/assets/common/images/newfile.png diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index aef88a223..35cfa45e7 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -21,7 +21,7 @@ @section / } @if(hasWritePermission){ - + + }
diff --git a/src/main/webapp/assets/common/images/newfile.png b/src/main/webapp/assets/common/images/newfile.png new file mode 100644 index 0000000000000000000000000000000000000000..e4b20fbf6edae3e90d651a2ad0524b4a65b2a8e2 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Hl8kyAr_~T6BdXSfFRMK|6J%5m PG>5^{)z4*}Q$iB}+KwWx literal 0 HcmV?d00001 From aae5fe387b9d69335e9b36380d8f6e007a0dbd98 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 29 Apr 2014 02:52:02 +0900 Subject: [PATCH 64/76] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f50c7d089..a0911fb50 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,12 @@ Run the following commands in `Terminal` to Release Notes -------- +### 1.13 - xx Apr 2014 +- Direct file editing in the repository viewer using AceEditor +- File attachment for issues +- Atom feed for user activities +- Fix some bugs + ### 1.12 - 29 Mar 2014 - SSH repository access is available - Allow users can create and management their groups From bdd0af21a9f5b877301a847673f4e27d1ce75fbe Mon Sep 17 00:00:00 2001 From: shimamoto Date: Tue, 29 Apr 2014 04:19:40 +0900 Subject: [PATCH 65/76] (refs #12) Created the helper of the attachment function. --- src/main/twirl/helper/attached.scala.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/twirl/helper/attached.scala.html diff --git a/src/main/twirl/helper/attached.scala.html b/src/main/twirl/helper/attached.scala.html new file mode 100644 index 000000000..6ac66b0db --- /dev/null +++ b/src/main/twirl/helper/attached.scala.html @@ -0,0 +1,22 @@ +@(owner: String, repository: String)(textarea: Html)(implicit context: app.Context) +@import context._ +
+ @textarea + Attach images by dragging & dropping, or selecting them. +
+@defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId => + +} From 93bf0a9a47b29f2288a8fcdc46e8fc16e0d0c475 Mon Sep 17 00:00:00 2001 From: shimamoto Date: Tue, 29 Apr 2014 04:21:48 +0900 Subject: [PATCH 66/76] (refs #12) Modified to use the helper of the attachment. --- src/main/twirl/helper/preview.scala.html | 28 +++++--------------- src/main/twirl/issues/editcomment.scala.html | 4 ++- src/main/twirl/issues/editissue.scala.html | 4 ++- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/main/twirl/helper/preview.scala.html b/src/main/twirl/helper/preview.scala.html index 7a7c0718d..639acd981 100644 --- a/src/main/twirl/helper/preview.scala.html +++ b/src/main/twirl/helper/preview.scala.html @@ -6,19 +6,18 @@
-
+ @textarea = { - Attach images by dragging & dropping, or selecting them. -
+ } + @if(enableWikiLink){ + @textarea + } else { + @helper.html.attached(repository.owner, repository.name)(textarea) + }
@@ -45,18 +44,5 @@ $(function(){ prettyPrint(); }); }); - - @if(!enableWikiLink){ - $("div#clickable").dropzone({ - url: '@path/upload/image/@repository.owner/@repository.name', - maxFilesize: 10, - previewTemplate: "
\n
Uploading your images...
\n
\n
", - success: function(file, id) { - var images = '\n![' + file.name.split('.')[0] + '](@url(repository)/_attached/' + id + ')'; - $('#content').val($('#content').val() + images); - file.previewElement.remove(); - } - }); - } }); diff --git a/src/main/twirl/issues/editcomment.scala.html b/src/main/twirl/issues/editcomment.scala.html index 1539f8162..e631872a9 100644 --- a/src/main/twirl/issues/editcomment.scala.html +++ b/src/main/twirl/issues/editcomment.scala.html @@ -1,7 +1,9 @@ @(content: String, commentId: Int, owner: String, repository: String)(implicit context: app.Context) @import context._ - +@helper.html.attached(owner, repository){ + +}
diff --git a/src/main/twirl/issues/editissue.scala.html b/src/main/twirl/issues/editissue.scala.html index 80554f79f..b97d1f5b6 100644 --- a/src/main/twirl/issues/editissue.scala.html +++ b/src/main/twirl/issues/editissue.scala.html @@ -2,7 +2,9 @@ @import context._ - +@helper.html.attached(owner, repository){ + +}
From 9f325290e8171c60f694c6fc008ea548d23655ec Mon Sep 17 00:00:00 2001 From: shimamoto Date: Tue, 29 Apr 2014 11:53:13 +0900 Subject: [PATCH 67/76] (refs #12) Modified from path to baseURL. --- src/main/twirl/helper/attached.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/twirl/helper/attached.scala.html b/src/main/twirl/helper/attached.scala.html index 6ac66b0db..07cf2dfca 100644 --- a/src/main/twirl/helper/attached.scala.html +++ b/src/main/twirl/helper/attached.scala.html @@ -12,7 +12,7 @@ $(function(){ maxFilesize: 10, previewTemplate: "
\n
Uploading your images...
\n
\n
", success: function(file, id) { - var images = '\n![' + file.name.split('.')[0] + '](@path/@owner/@repository/_attached/' + id + ')'; + var images = '\n![' + file.name.split('.')[0] + '](@baseURL/@owner/@repository/_attached/' + id + ')'; $('#@textareaId').val($('#@textareaId').val() + images); file.previewElement.remove(); } From 790eee74434b7e28fc7ec3468805a5235a11b705 Mon Sep 17 00:00:00 2001 From: shimamoto Date: Tue, 29 Apr 2014 13:41:50 +0900 Subject: [PATCH 68/76] (refs #12) Add acceptedFiles options. --- src/main/twirl/helper/attached.scala.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/twirl/helper/attached.scala.html b/src/main/twirl/helper/attached.scala.html index 07cf2dfca..7ae78c9f9 100644 --- a/src/main/twirl/helper/attached.scala.html +++ b/src/main/twirl/helper/attached.scala.html @@ -10,11 +10,13 @@ $(function(){ $('#@textareaId').closest('div').dropzone({ url: '@path/upload/image/@owner/@repository', maxFilesize: 10, + acceptedFiles: 'image/*', + dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, or JPG.', previewTemplate: "
\n
Uploading your images...
\n
\n
", success: function(file, id) { var images = '\n![' + file.name.split('.')[0] + '](@baseURL/@owner/@repository/_attached/' + id + ')'; $('#@textareaId').val($('#@textareaId').val() + images); - file.previewElement.remove(); + $(file.previewElement).prevAll('div.dz-preview').addBack().remove(); } }); }); From 52ebba43d55d4a40eaeea130ce8e4dde8f8e559e Mon Sep 17 00:00:00 2001 From: shimamoto Date: Tue, 29 Apr 2014 13:54:06 +0900 Subject: [PATCH 69/76] (refs #12) Modified the response status when the file does not exist. --- src/main/scala/app/IssuesController.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index a72fd78eb..490d018d9 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -274,7 +274,9 @@ trait IssuesControllerBase extends ControllerBase { }) get("/:owner/:repository/_attached/:file")(referrersOnly { repository => - new java.io.File(Directory.getAttachedDir(repository.owner, repository.name), params("file")) + defining(new java.io.File(Directory.getAttachedDir(repository.owner, repository.name), params("file"))){ file => + if(file.exists) file else NotFound + } }) val assignedUserName = (key: String) => params.get(key) filter (_.trim != "") From 0f9c95c15a408d589e6587b0d7351a1f3b0dc800 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Apr 2014 15:43:41 +0900 Subject: [PATCH 70/76] baseUrl calculation is concentrated to SystemSettings --- src/main/scala/app/AccountController.scala | 4 ++-- src/main/scala/app/ControllerBase.scala | 4 ++-- src/main/scala/app/DashboardController.scala | 4 ++-- src/main/scala/app/IndexController.scala | 4 ++-- src/main/scala/app/IssuesController.scala | 6 +++--- src/main/scala/app/PullRequestsController.scala | 14 +++++++------- .../scala/app/RepositoryViewerController.scala | 2 +- src/main/scala/service/SystemSettingsService.scala | 14 ++++++++------ src/main/twirl/helper/feed.scala.xml | 6 +++--- 9 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index ca513d451..72244dc90 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -112,7 +112,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { val members = getGroupMembers(account.userName) _root_.account.html.repositories(account, if(account.isGroupAccount) Nil else getGroupsByUserName(userName), - getVisibleRepositories(context.loginAccount, baseUrl, Some(userName)), + getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)), context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })) } } @@ -292,7 +292,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { */ post("/new", newRepositoryForm)(usersOnly { form => LockUtil.lock(s"${form.owner}/${form.name}/create"){ - if(getRepository(form.owner, form.name, baseUrl).isEmpty){ + if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){ val ownerAccount = getAccountByUserName(form.owner).get val loginAccount = context.loginAccount.get val loginUserName = loginAccount.userName diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index 1b2e0f905..34ad4cefe 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -141,8 +141,8 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: val path = settings.baseUrl.getOrElse(request.getServletContext.getContextPath) val currentPath = request.getRequestURI.substring(request.getContextPath.length) - val baseURL = settings.baseUrl.getOrElse(request.getRequestURL.substring(0, request.getRequestURL.length - request.getRequestURI.length)) - val host = new java.net.URL(baseURL).getHost + val baseUrl = settings.baseUrl(request) + val host = new java.net.URL(baseUrl).getHost /** * Get object from cache. diff --git a/src/main/scala/app/DashboardController.scala b/src/main/scala/app/DashboardController.scala index 871dd34c4..8fe73bf30 100644 --- a/src/main/scala/app/DashboardController.scala +++ b/src/main/scala/app/DashboardController.scala @@ -49,7 +49,7 @@ trait DashboardControllerBase extends ControllerBase { ) val userName = context.loginAccount.get.userName - val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name) + val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name) val filterUser = Map(filter -> userName) val page = IssueSearchCondition.page(request) // @@ -80,7 +80,7 @@ trait DashboardControllerBase extends ControllerBase { }.copy(repo = repository)) val userName = context.loginAccount.get.userName - val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name) + val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name) val filterUser = Map(filter -> userName) val page = IssueSearchCondition.page(request) diff --git a/src/main/scala/app/IndexController.scala b/src/main/scala/app/IndexController.scala index a51025d4b..6cc95b6a3 100644 --- a/src/main/scala/app/IndexController.scala +++ b/src/main/scala/app/IndexController.scala @@ -21,8 +21,8 @@ trait IndexControllerBase extends ControllerBase { val loginAccount = context.loginAccount html.index(getRecentActivities(), - getVisibleRepositories(loginAccount, baseUrl), - loginAccount.map{ account => getUserRepositories(account.userName, baseUrl) }.getOrElse(Nil) + getVisibleRepositories(loginAccount, context.baseUrl), + loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl) }.getOrElse(Nil) ) } diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index be564ee39..7dc634c0b 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -118,7 +118,7 @@ trait IssuesControllerBase extends ControllerBase { // notifications Notifier().toNotify(repository, issueId, form.content.getOrElse("")){ - Notifier.msgIssue(s"${baseUrl}/${owner}/${name}/issues/${issueId}") + Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") } redirect(s"/${owner}/${name}/issues/${issueId}") @@ -342,13 +342,13 @@ trait IssuesControllerBase extends ControllerBase { case f => content foreach { f.toNotify(repository, issueId, _){ - Notifier.msgComment(s"${baseUrl}/${owner}/${name}/${ + Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${ if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}") } } action foreach { f.toNotify(repository, issueId, _){ - Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/issues/${issueId}") + Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") } } } diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index 5e2c6bcdc..a7c85cdcb 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -100,7 +100,7 @@ trait PullRequestsControllerBase extends ControllerBase { pulls.html.mergeguide( checkConflictInPullRequest(owner, name, pullreq.branch, pullreq.requestUserName, name, pullreq.requestBranch, issueId), pullreq, - s"${baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git") + s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git") } } getOrElse NotFound }) @@ -178,7 +178,7 @@ trait PullRequestsControllerBase extends ControllerBase { pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo) // close issue by content of pull request - val defaultBranch = getRepository(owner, name, baseUrl).get.repository.defaultBranch + val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch if(pullreq.branch == defaultBranch){ commits.flatten.foreach { commit => closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) @@ -201,7 +201,7 @@ trait PullRequestsControllerBase extends ControllerBase { // notifications Notifier().toNotify(repository, issueId, "merge"){ - Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/pull/${issueId}") + Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") } redirect(s"/${owner}/${name}/pull/${issueId}") @@ -214,7 +214,7 @@ trait PullRequestsControllerBase extends ControllerBase { get("/:owner/:repository/compare")(referrersOnly { forkedRepository => (forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { case (Some(originUserName), Some(originRepositoryName)) => { - getRepository(originUserName, originRepositoryName, baseUrl).map { originRepository => + getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository => using( Git.open(getRepositoryDir(originUserName, originRepositoryName)), Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) @@ -251,7 +251,7 @@ trait PullRequestsControllerBase extends ControllerBase { getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2) } }; - originRepository <- getRepository(originOwner, originRepositoryName, baseUrl) + originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl) ) yield { using( Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), @@ -303,7 +303,7 @@ trait PullRequestsControllerBase extends ControllerBase { getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2) } }; - originRepository <- getRepository(originOwner, originRepositoryName, baseUrl) + originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl) ) yield { using( Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), @@ -356,7 +356,7 @@ trait PullRequestsControllerBase extends ControllerBase { // notifications Notifier().toNotify(repository, issueId, form.content.getOrElse("")){ - Notifier.msgPullRequest(s"${baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}") + Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}") } redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index d13f9bec6..6798c0a92 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -304,7 +304,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { getRepository( repository.repository.originUserName.getOrElse(repository.owner), repository.repository.originRepositoryName.getOrElse(repository.name), - baseUrl), + context.baseUrl), getForkedRepositories( repository.repository.originUserName.getOrElse(repository.owner), repository.repository.originRepositoryName.getOrElse(repository.name)), diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala index ba425a3ce..fd72b60e7 100644 --- a/src/main/scala/service/SystemSettingsService.scala +++ b/src/main/scala/service/SystemSettingsService.scala @@ -7,11 +7,7 @@ import javax.servlet.http.HttpServletRequest trait SystemSettingsService { - def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl.getOrElse { - defining(request.getRequestURL.toString){ url => - url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length)) - } - }.replaceFirst("/$", "") + def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request) def saveSystemSettings(settings: SystemSettings): Unit = { defining(new java.util.Properties()){ props => @@ -110,7 +106,13 @@ object SystemSettingsService { sshPort: Option[Int], smtp: Option[Smtp], ldapAuthentication: Boolean, - ldap: Option[Ldap]) + ldap: Option[Ldap]){ + def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse { + defining(request.getRequestURL.toString){ url => + url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length)) + } + }.replaceFirst("/$", "") + } case class Ldap( host: String, diff --git a/src/main/twirl/helper/feed.scala.xml b/src/main/twirl/helper/feed.scala.xml index f870ca3eb..3a8e3791b 100644 --- a/src/main/twirl/helper/feed.scala.xml +++ b/src/main/twirl/helper/feed.scala.xml @@ -8,10 +8,10 @@ @header(activities: List[model.Activity]) = {tag:@context.host,2013:gitbucket Gitbucket's activities - + Gitbucket - @context.baseURL + @context.baseUrl @datetimeRFC3339(activities.map(_.activityDate).max)} @@ -20,7 +20,7 @@ tag:@context.host,@date(activity.activityDate):activity:@activity.activityId @datetimeRFC3339(activity.activityDate) @datetimeRFC3339(activity.activityDate) - + @activity.activityType @activity.activityUserName From 9a0cc9e043a2682aa26144ab63ae459078ebfe2a Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Apr 2014 15:52:24 +0900 Subject: [PATCH 71/76] Fix baseURL to baseUrl --- src/main/twirl/helper/attached.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/twirl/helper/attached.scala.html b/src/main/twirl/helper/attached.scala.html index 7ae78c9f9..809d06048 100644 --- a/src/main/twirl/helper/attached.scala.html +++ b/src/main/twirl/helper/attached.scala.html @@ -14,7 +14,7 @@ $(function(){ dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, or JPG.', previewTemplate: "
\n
Uploading your images...
\n
\n
", success: function(file, id) { - var images = '\n![' + file.name.split('.')[0] + '](@baseURL/@owner/@repository/_attached/' + id + ')'; + var images = '\n![' + file.name.split('.')[0] + '](@baseUrl/@owner/@repository/_attached/' + id + ')'; $('#@textareaId').val($('#@textareaId').val() + images); $(file.previewElement).prevAll('div.dz-preview').addBack().remove(); } From ecfaa0247a4e8d603b938bd137ce3c1c355c9dd9 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Apr 2014 16:42:40 +0900 Subject: [PATCH 72/76] (refs #12)Fix styles --- src/main/twirl/helper/attached.scala.html | 9 ++++++--- .../webapp/assets/common/css/gitbucket.css | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/twirl/helper/attached.scala.html b/src/main/twirl/helper/attached.scala.html index 809d06048..a7a36f24c 100644 --- a/src/main/twirl/helper/attached.scala.html +++ b/src/main/twirl/helper/attached.scala.html @@ -1,13 +1,13 @@ @(owner: String, repository: String)(textarea: Html)(implicit context: app.Context) @import context._ -
+
@textarea - Attach images by dragging & dropping, or selecting them. +
Attach images by dragging & dropping, or selecting them.
@defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId => } diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index 272eae158..eef2d0470 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -671,6 +671,26 @@ div.issue-comment-action { border-radius: 5px; } +div.attachable { + margin-bottom: 10px; +} + +div.attachable textarea { + margin-bottom: 0px; + border-bottom: 1px dashed #ccc; + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; +} + +div.attachable div { + padding: 2px; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + background-color: white; +} + /****************************************************************************/ /* Pull Request */ /****************************************************************************/ From 2fe6b8c1e7bb1af417be92b55ee5a098ebf2c398 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Apr 2014 17:02:16 +0900 Subject: [PATCH 73/76] Fix TestCase --- src/main/scala/app/ControllerBase.scala | 2 +- .../scala/view/AvatarImageProviderSpec.scala | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index 5a2a4b063..6abacc7e3 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -138,7 +138,7 @@ abstract class ControllerBase extends ScalatraFilter */ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){ - val path = settings.baseUrl.getOrElse(request.getServletContext.getContextPath) + val path = settings.baseUrl.getOrElse(request.getContextPath) val currentPath = request.getRequestURI.substring(request.getContextPath.length) val baseUrl = settings.baseUrl(request) val host = new java.net.URL(baseUrl).getHost diff --git a/src/test/scala/view/AvatarImageProviderSpec.scala b/src/test/scala/view/AvatarImageProviderSpec.scala index 079771a6f..8716a76de 100644 --- a/src/test/scala/view/AvatarImageProviderSpec.scala +++ b/src/test/scala/view/AvatarImageProviderSpec.scala @@ -3,16 +3,23 @@ package view import java.util.Date import org.specs2.mutable._ +import org.specs2.mock.Mockito import service.RequestCache import model.Account import service.SystemSettingsService.SystemSettings import twirl.api.Html +import javax.servlet.http.HttpServletRequest -class AvatarImageProviderSpec extends Specification { +class AvatarImageProviderSpec extends Specification with Mockito { + + val request = mock[HttpServletRequest] + request.getRequestURL returns new StringBuffer("http://localhost:8080/path.html") + request.getRequestURI returns "/path.html" + request.getContextPath returns "" "getAvatarImageHtml" should { "show Gravatar image for no image account if gravatar integration is enabled" in { - implicit val context = app.Context(createSystemSettings(true), None, null) + implicit val context = app.Context(createSystemSettings(true), None, request) val provider = new AvatarImageProviderImpl(Some(createAccount(None))) provider.toHtml("user", 32).toString mustEqual @@ -20,7 +27,7 @@ class AvatarImageProviderSpec extends Specification { } "show uploaded image even if gravatar integration is enabled" in { - implicit val context = app.Context(createSystemSettings(true), None, null) + implicit val context = app.Context(createSystemSettings(true), None, request) val provider = new AvatarImageProviderImpl(Some(createAccount(Some("icon.png")))) provider.toHtml("user", 32).toString mustEqual @@ -28,7 +35,7 @@ class AvatarImageProviderSpec extends Specification { } "show local image for no image account if gravatar integration is disabled" in { - implicit val context = app.Context(createSystemSettings(false), None, null) + implicit val context = app.Context(createSystemSettings(false), None, request) val provider = new AvatarImageProviderImpl(Some(createAccount(None))) provider.toHtml("user", 32).toString mustEqual @@ -36,7 +43,7 @@ class AvatarImageProviderSpec extends Specification { } "show Gravatar image for specified mail address if gravatar integration is enabled" in { - implicit val context = app.Context(createSystemSettings(true), None, null) + implicit val context = app.Context(createSystemSettings(true), None, request) val provider = new AvatarImageProviderImpl(None) provider.toHtml("user", 20, "hoge@hoge.com").toString mustEqual @@ -44,7 +51,7 @@ class AvatarImageProviderSpec extends Specification { } "show unknown image for unknown user if gravatar integration is enabled" in { - implicit val context = app.Context(createSystemSettings(true), None, null) + implicit val context = app.Context(createSystemSettings(true), None, request) val provider = new AvatarImageProviderImpl(None) provider.toHtml("user", 20).toString mustEqual @@ -52,7 +59,7 @@ class AvatarImageProviderSpec extends Specification { } "show unknown image for specified mail address if gravatar integration is disabled" in { - implicit val context = app.Context(createSystemSettings(false), None, null) + implicit val context = app.Context(createSystemSettings(false), None, request) val provider = new AvatarImageProviderImpl(None) provider.toHtml("user", 20, "hoge@hoge.com").toString mustEqual @@ -60,7 +67,7 @@ class AvatarImageProviderSpec extends Specification { } "add tooltip if it's enabled" in { - implicit val context = app.Context(createSystemSettings(false), None, null) + implicit val context = app.Context(createSystemSettings(false), None, request) val provider = new AvatarImageProviderImpl(None) provider.toHtml("user", 20, "hoge@hoge.com", true).toString mustEqual @@ -85,7 +92,7 @@ class AvatarImageProviderSpec extends Specification { private def createSystemSettings(useGravatar: Boolean) = SystemSettings( - baseUrl = Some(""), + baseUrl = None, allowAccountRegistration = false, gravatar = useGravatar, notification = false, From 1900aefe320ef143f668489738522946d577f54a Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 29 Apr 2014 17:24:16 +0900 Subject: [PATCH 74/76] Modify message in avatar uploader --- src/main/twirl/helper/uploadavatar.scala.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/twirl/helper/uploadavatar.scala.html b/src/main/twirl/helper/uploadavatar.scala.html index 5de7ae47a..06bc06e8f 100644 --- a/src/main/twirl/helper/uploadavatar.scala.html +++ b/src/main/twirl/helper/uploadavatar.scala.html @@ -4,9 +4,7 @@ @if(account.nonEmpty && account.get.image.nonEmpty){ } else { -
- Gravatar is used -
+
Upload Image
}
@if(account.nonEmpty && account.get.image.nonEmpty){ From dd694d27b5ca024647d699af2d6a95a385bac21d Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Tue, 29 Apr 2014 16:57:01 +0900 Subject: [PATCH 75/76] (refs #324)Update commitIdFrom when pullrequest branch is updated --- src/main/scala/app/PullRequestsController.scala | 13 +------------ src/main/scala/service/PullRequestService.scala | 3 +++ src/main/scala/servlet/GitRepositoryServlet.scala | 13 ++++++++++--- src/main/scala/util/JGitUtil.scala | 11 +++++++++++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index a7c85cdcb..0f0860215 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -260,7 +260,7 @@ trait PullRequestsControllerBase extends ControllerBase { val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2 val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2 - val forkedId = getForkedCommitId(oldGit, newGit, + val forkedId = JGitUtil.getForkedCommitId(oldGit, newGit, originRepository.owner, originRepository.name, originBranch, forkedRepository.owner, forkedRepository.name, forkedBranch) @@ -432,17 +432,6 @@ trait PullRequestsControllerBase extends ControllerBase { (defaultOwner, value) } - /** - * Returns the identifier of the root commit (or latest merge commit) of the specified branch. - */ - private def getForkedCommitId(oldGit: Git, newGit: Git, userName: String, repositoryName: String, branch: String, - requestUserName: String, requestRepositoryName: String, requestBranch: String): String = - defining(JGitUtil.getAllCommitIds(oldGit)){ existIds => - JGitUtil.getCommitLogs(newGit, requestBranch, true) { commit => - existIds.contains(commit.name) && JGitUtil.getBranchesOfCommit(oldGit, commit.getName).contains(branch) - }.head.id - } - private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String, requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = using( diff --git a/src/main/scala/service/PullRequestService.scala b/src/main/scala/service/PullRequestService.scala index 7d11f08dc..12984938b 100644 --- a/src/main/scala/service/PullRequestService.scala +++ b/src/main/scala/service/PullRequestService.scala @@ -18,6 +18,9 @@ trait PullRequestService { self: IssuesService => def updateCommitIdTo(owner: String, repository: String, issueId: Int, commitIdTo: String): Unit = Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).map(_.commitIdTo).update(commitIdTo) + def updateCommitIdFrom(owner: String, repository: String, issueId: Int, commitIdFrom: String): Unit = + Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).map(_.commitIdFrom).update(commitIdFrom) + def getPullRequestCountGroupByUser(closed: Boolean, owner: String, repository: Option[String]): List[PullRequestCount] = Query(PullRequests) .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala index 389c1af2b..94f0521b0 100644 --- a/src/main/scala/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/servlet/GitRepositoryServlet.scala @@ -218,14 +218,21 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: private def updatePullRequests(branch: String) = getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq => if(getRepository(pullreq.userName, pullreq.repositoryName, baseUrl).isDefined){ - using(Git.open(Directory.getRepositoryDir(pullreq.userName, pullreq.repositoryName))){ git => - git.fetch + using(Git.open(Directory.getRepositoryDir(pullreq.userName, pullreq.repositoryName)), + Git.open(Directory.getRepositoryDir(pullreq.requestUserName, pullreq.requestRepositoryName))){ (oldGit, newGit) => + oldGit.fetch .setRemote(Directory.getRepositoryDir(owner, repository).toURI.toString) .setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/pull/${pullreq.issueId}/head").setForceUpdate(true)) .call - val commitIdTo = git.getRepository.resolve(s"refs/pull/${pullreq.issueId}/head").getName + val commitIdTo = oldGit.getRepository.resolve(s"refs/pull/${pullreq.issueId}/head").getName updateCommitIdTo(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo) + + val commitIdFrom = JGitUtil.getForkedCommitId(oldGit, newGit, + pullreq.userName, pullreq.repositoryName, pullreq.branch, + pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch) + // TODO(tanacasino): commitIdFrom and commitIdTo should be updated by one query... + updateCommitIdFrom(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdFrom) } } } diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 97e8c419f..9f48c39c7 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -617,5 +617,16 @@ object JGitUtil { } } + /** + * Returns the identifier of the root commit (or latest merge commit) of the specified branch. + */ + def getForkedCommitId(oldGit: Git, newGit: Git, + userName: String, repositoryName: String, branch: String, + requestUserName: String, requestRepositoryName: String, requestBranch: String): String = + defining(getAllCommitIds(oldGit)){ existIds => + getCommitLogs(newGit, requestBranch, true) { commit => + existIds.contains(commit.name) && getBranchesOfCommit(oldGit, commit.getName).contains(branch) + }.head.id + } } From 3e82534c78a72e17dd3b79e091521d75cb4d3855 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 29 Apr 2014 17:40:53 +0900 Subject: [PATCH 76/76] Update README.md --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a0911fb50..8faba94b0 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,15 @@ GitBucket GitBucket is the easily installable Github clone written with Scala. +[![Gitter chat](https://badges.gitter.im/takezoe/gitbucket.png)](https://gitter.im/takezoe/gitbucket) + + +Features +-------- The current version of GitBucket provides a basic features below: - Public / Private Git repository (http access only) -- Repository viewer (some advanced features such as online file editing are not implemented) +- Repository viewer and online file editing - Repository search (Code and Issues) - Wiki - Issues @@ -20,7 +25,6 @@ The current version of GitBucket provides a basic features below: Following features are not implemented, but we will make them in the future release! -- File editing in repository viewer - Comment for the changeset - Network graph - Statistics @@ -78,7 +82,7 @@ Run the following commands in `Terminal` to Release Notes -------- -### 1.13 - xx Apr 2014 +### 1.13 - 29 Apr 2014 - Direct file editing in the repository viewer using AceEditor - File attachment for issues - Atom feed for user activities