diff --git a/README.md b/README.md
index a902ad6..e229137 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
GitBucket [](https://gitter.im/takezoe/gitbucket) [](https://travis-ci.org/takezoe/gitbucket)
=========
-GitBucket is the easily installable Github clone written with Scala.
+GitBucket is the easily installable GitHub clone powered by Scala.
Features
@@ -79,6 +79,13 @@
Release Notes
--------
+### 2.8 - 1 Feb 2015
+- New logo and icons
+- New system setting options to control visibility
+- Comment on side-by-side diff
+- Information message on sign-in page
+- Fork repository by group account
+
### 2.7 - 29 Dec 2014
- Comment for commit and diff
- Fix security issue in markdown rendering
diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala
index 90fe415..bdbd898 100644
--- a/src/main/scala/ScalatraBootstrap.scala
+++ b/src/main/scala/ScalatraBootstrap.scala
@@ -14,6 +14,7 @@
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
// Register controllers
+ context.mount(new AnonymousAccessController, "/*")
context.mount(new IndexController, "/")
context.mount(new SearchController, "/")
context.mount(new FileUploadController, "/upload")
diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala
index 732e18d..8311654 100644
--- a/src/main/scala/app/AccountController.scala
+++ b/src/main/scala/app/AccountController.scala
@@ -135,8 +135,7 @@
get("/:userName/_avatar"){
val userName = params("userName")
getAccountByUserName(userName).flatMap(_.image).map { image =>
- contentType = FileUtil.getMimeType(image)
- new java.io.File(getUserUploadDir(userName), image)
+ RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))
} getOrElse {
contentType = "image/png"
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
diff --git a/src/main/scala/app/AnonymousAccessController.scala b/src/main/scala/app/AnonymousAccessController.scala
new file mode 100644
index 0000000..35481ab
--- /dev/null
+++ b/src/main/scala/app/AnonymousAccessController.scala
@@ -0,0 +1,14 @@
+package app
+
+class AnonymousAccessController extends AnonymousAccessControllerBase
+
+trait AnonymousAccessControllerBase extends ControllerBase {
+ get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
+ if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
+ !context.currentPath.startsWith("/register")) {
+ Unauthorized()
+ } else {
+ pass()
+ }
+ }
+}
diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala
index c31b90f..8a184f0 100644
--- a/src/main/scala/app/ControllerBase.scala
+++ b/src/main/scala/app/ControllerBase.scala
@@ -134,6 +134,18 @@
if (path.startsWith("http")) path
else baseUrl + super.url(path, params, false, false, false)
+ /**
+ * Use this method to response the raw data against XSS.
+ */
+ protected def RawData[T](contentType: String, rawData: T): T = {
+ if(contentType.split(";").head.trim.toLowerCase.startsWith("text/html")){
+ this.contentType = "text/plain"
+ } else {
+ this.contentType = contentType
+ }
+ response.addHeader("X-Content-Type-Options", "nosniff")
+ rawData
+ }
}
/**
diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala
index 9f6efcf..063b8b2 100644
--- a/src/main/scala/app/IssuesController.scala
+++ b/src/main/scala/app/IssuesController.scala
@@ -292,8 +292,7 @@
(Directory.getAttachedDir(repository.owner, repository.name) match {
case dir if(dir.exists && dir.isDirectory) =>
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
- contentType = FileUtil.getMimeType(file.getName)
- file
+ RawData(FileUtil.getMimeType(file.getName), file)
}
case _ => None
}) getOrElse NotFound
diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala
index 00f6755..57b1027 100644
--- a/src/main/scala/app/RepositoryViewerController.scala
+++ b/src/main/scala/app/RepositoryViewerController.scala
@@ -21,7 +21,7 @@
class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService
/**
@@ -29,7 +29,7 @@
*/
trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService =>
ArchiveCommand.registerFormat("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
@@ -214,8 +214,7 @@
if(raw){
// Download
defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes =>
- contentType = FileUtil.getContentType(path, bytes)
- bytes
+ RawData(FileUtil.getContentType(path, bytes), bytes)
}
} else {
repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
@@ -496,6 +495,9 @@
//refUpdate.setRefLogMessage("merged", true)
refUpdate.update()
+ // update pull request
+ updatePullRequests(repository.owner, repository.name, branch)
+
// record activity
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala
index 5c11959..e278433 100644
--- a/src/main/scala/app/SystemSettingsController.scala
+++ b/src/main/scala/app/SystemSettingsController.scala
@@ -16,6 +16,7 @@
"baseUrl" -> trim(label("Base URL", optional(text()))),
"information" -> trim(label("Information", optional(text()))),
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
+ "allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
diff --git a/src/main/scala/app/WikiController.scala b/src/main/scala/app/WikiController.scala
index 5270d03..c273018 100644
--- a/src/main/scala/app/WikiController.scala
+++ b/src/main/scala/app/WikiController.scala
@@ -164,8 +164,7 @@
val path = multiParams("splat").head
getFileContent(repository.owner, repository.name, path).map { bytes =>
- contentType = FileUtil.getContentType(path, bytes)
- bytes
+ RawData(FileUtil.getContentType(path, bytes), bytes)
} getOrElse NotFound
})
diff --git a/src/main/scala/service/PullRequestService.scala b/src/main/scala/service/PullRequestService.scala
index 9a3239b..68121f9 100644
--- a/src/main/scala/service/PullRequestService.scala
+++ b/src/main/scala/service/PullRequestService.scala
@@ -3,6 +3,7 @@
import model.Profile._
import profile.simple._
import model.{PullRequest, Issue}
+import util.JGitUtil
trait PullRequestService { self: IssuesService =>
import PullRequestService._
@@ -81,6 +82,18 @@
.map { case (t1, t2) => t1 }
.list
+ /**
+ * Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
+ */
+ def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit =
+ getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq =>
+ if(Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run){
+ val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest(
+ pullreq.userName, pullreq.repositoryName, pullreq.branch, pullreq.issueId,
+ pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)
+ updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
+ }
+ }
}
object PullRequestService {
diff --git a/src/main/scala/service/RepositoryService.scala b/src/main/scala/service/RepositoryService.scala
index 6894125..f54291b 100644
--- a/src/main/scala/service/RepositoryService.scala
+++ b/src/main/scala/service/RepositoryService.scala
@@ -2,7 +2,7 @@
import model.Profile._
import profile.simple._
-import model.{Repository, Account, Collaborator}
+import model.{Repository, Account, Collaborator, Label}
import util.JGitUtil
trait RepositoryService { self: AccountService =>
@@ -94,9 +94,17 @@
PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
- IssueLabels .insertAll(issueLabels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
CommitComments.insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
+ // Convert labelId
+ val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap
+ val newLabelMap = Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap
+ IssueLabels.insertAll(issueLabels.map(x => x.copy(
+ labelId = newLabelMap(oldLabelMap(x.labelId)),
+ userName = newUserName,
+ repositoryName = newRepositoryName
+ )) :_*)
+
if(account.isGroupAccount){
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
} else {
diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala
index 7575094..156a23b 100644
--- a/src/main/scala/service/SystemSettingsService.scala
+++ b/src/main/scala/service/SystemSettingsService.scala
@@ -14,6 +14,7 @@
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
settings.information.foreach(x => props.setProperty(Information, x))
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
+ props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
props.setProperty(Gravatar, settings.gravatar.toString)
props.setProperty(Notification, settings.notification.toString)
@@ -65,6 +66,7 @@
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
getOptionValue[String](props, Information, None),
getValue(props, AllowAccountRegistration, false),
+ getValue(props, AllowAnonymousAccess, true),
getValue(props, IsCreateRepoOptionPublic, true),
getValue(props, Gravatar, true),
getValue(props, Notification, false),
@@ -113,6 +115,7 @@
baseUrl: Option[String],
information: Option[String],
allowAccountRegistration: Boolean,
+ allowAnonymousAccess: Boolean,
isCreateRepoOptionPublic: Boolean,
gravatar: Boolean,
notification: Boolean,
@@ -158,6 +161,7 @@
private val BaseURL = "base_url"
private val Information = "information"
private val AllowAccountRegistration = "allow_account_registration"
+ private val AllowAnonymousAccess = "allow_anonymous_access"
private val IsCreateRepoOptionPublic = "is_create_repository_option_public"
private val Gravatar = "gravatar"
private val Notification = "notification"
diff --git a/src/main/scala/servlet/BasicAuthenticationFilter.scala b/src/main/scala/servlet/BasicAuthenticationFilter.scala
index 8272c7a..cbecfc1 100644
--- a/src/main/scala/servlet/BasicAuthenticationFilter.scala
+++ b/src/main/scala/servlet/BasicAuthenticationFilter.scala
@@ -28,33 +28,45 @@
override def setCharacterEncoding(encoding: String) = {}
}
+ val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals(request.getQueryString)
+
+ val settings = loadSystemSettings()
+
try {
- defining(request.paths){ case Array(_, repositoryOwner, repositoryName, _*) =>
- getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
- case Some(repository) => {
- if(!request.getRequestURI.endsWith("/git-receive-pack") &&
- !"service=git-receive-pack".equals(request.getQueryString) && !repository.repository.isPrivate){
- chain.doFilter(req, wrappedResponse)
- } else {
- request.getHeader("Authorization") match {
- case null => requireAuth(response)
- case auth => decodeAuthHeader(auth).split(":") match {
- case Array(username, password) => getWritableUser(username, password, repository) match {
- case Some(account) => {
- request.setAttribute(Keys.Request.UserName, account.userName)
- chain.doFilter(req, wrappedResponse)
+ defining(request.paths){
+ case Array(_, repositoryOwner, repositoryName, _*) =>
+ getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
+ case Some(repository) => {
+ if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
+ chain.doFilter(req, wrappedResponse)
+ } else {
+ request.getHeader("Authorization") match {
+ case null => requireAuth(response)
+ case auth => decodeAuthHeader(auth).split(":") match {
+ case Array(username, password) => {
+ authenticate(settings, username, password) match {
+ case Some(account) => {
+ if(isUpdating && hasWritePermission(repository.owner, repository.name, Some(account))){
+ request.setAttribute(Keys.Request.UserName, account.userName)
+ }
+ chain.doFilter(req, wrappedResponse)
+ }
+ case None => requireAuth(response)
+ }
}
- case None => requireAuth(response)
+ case _ => requireAuth(response)
}
- case _ => requireAuth(response)
}
}
}
+ case None => {
+ logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
+ response.sendError(HttpServletResponse.SC_NOT_FOUND)
+ }
}
- case None => {
- logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
- response.sendError(HttpServletResponse.SC_NOT_FOUND)
- }
+ case _ => {
+ logger.debug(s"Not enough path arguments: ${request.paths}")
+ response.sendError(HttpServletResponse.SC_NOT_FOUND)
}
}
} catch {
@@ -65,13 +77,6 @@
}
}
- private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo)
- (implicit session: Session): Option[Account] =
- authenticate(loadSystemSettings(), username, password) match {
- case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x
- case _ => None
- }
-
private def requireAuth(response: HttpServletResponse): Unit = {
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala
index df55d46..7fde407 100644
--- a/src/main/scala/servlet/GitRepositoryServlet.scala
+++ b/src/main/scala/servlet/GitRepositoryServlet.scala
@@ -174,7 +174,7 @@
case ReceiveCommand.Type.CREATE |
ReceiveCommand.Type.UPDATE |
ReceiveCommand.Type.UPDATE_NONFASTFORWARD =>
- updatePullRequests(branchName)
+ updatePullRequests(owner, repository, branchName)
case _ =>
}
}
@@ -211,26 +211,4 @@
}
}
}
-
- /**
- * Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
- */
- 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.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 = oldGit.getRepository.resolve(s"refs/pull/${pullreq.issueId}/head").getName
- val commitIdFrom = JGitUtil.getForkedCommitId(oldGit, newGit,
- pullreq.userName, pullreq.repositoryName, pullreq.branch,
- pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)
- updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
- }
- }
- }
}
diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala
index b5e2e3b..0f76f1e 100644
--- a/src/main/scala/util/JGitUtil.scala
+++ b/src/main/scala/util/JGitUtil.scala
@@ -13,6 +13,7 @@
import org.eclipse.jgit.treewalk.filter._
import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
+import org.eclipse.jgit.transport.RefSpec
import java.util.Date
import org.eclipse.jgit.api.errors.{JGitInternalException, InvalidRefNameException, RefAlreadyExistsException, NoHeadException}
import service.RepositoryService
@@ -675,6 +676,25 @@
}
/**
+ * Fetch pull request contents into refs/pull/${issueId}/head and return (commitIdTo, commitIdFrom)
+ */
+ def updatePullRequest(userName: String, repositoryName:String, branch: String, issueId: Int,
+ requestUserName: String, requestRepositoryName: String, requestBranch: String):(String, String) =
+ using(Git.open(Directory.getRepositoryDir(userName, repositoryName)),
+ Git.open(Directory.getRepositoryDir(requestUserName, requestRepositoryName))){ (oldGit, newGit) =>
+ oldGit.fetch
+ .setRemote(Directory.getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString)
+ .setRefSpecs(new RefSpec(s"refs/heads/${requestBranch}:refs/pull/${issueId}/head").setForceUpdate(true))
+ .call
+
+ val commitIdTo = oldGit.getRepository.resolve(s"refs/pull/${issueId}/head").getName
+ val commitIdFrom = getForkedCommitId(oldGit, newGit,
+ userName, repositoryName, branch,
+ requestUserName, requestRepositoryName, requestBranch)
+ (commitIdTo, commitIdFrom)
+ }
+
+ /**
* Returns the last modified commit of specified path
* @param git the Git object
* @param startCommit the search base commit id
diff --git a/src/main/scala/view/Markdown.scala b/src/main/scala/view/Markdown.scala
index 87719b4..836e5c9 100644
--- a/src/main/scala/view/Markdown.scala
+++ b/src/main/scala/view/Markdown.scala
@@ -195,6 +195,32 @@
printTag(node, "li")
}
}
+
+ override def visit(node: ExpLinkNode) {
+ printLink(linkRenderer.render(node, printLinkChildrenToString(node)))
+ }
+
+ def printLinkChildrenToString(node: SuperNode) = {
+ val priorPrinter = printer
+ printer = new Printer()
+ visitLinkChildren(node)
+ val result = printer.getString()
+ printer = priorPrinter
+ result
+ }
+
+ def visitLinkChildren(node: SuperNode) {
+ import scala.collection.JavaConversions._
+ node.getChildren.foreach(child => child match {
+ case node: ExpImageNode => visitLinkChild(node)
+ case node: SuperNode => visitLinkChildren(node)
+ case _ => child.accept(this)
+ })
+ }
+
+ def visitLinkChild(node: ExpImageNode) {
+ printer.print("")
+ }
}
object GitBucketHtmlSerializer {
diff --git a/src/main/twirl/admin/system.scala.html b/src/main/twirl/admin/system.scala.html
index 09c8ac8..194a164 100644
--- a/src/main/twirl/admin/system.scala.html
+++ b/src/main/twirl/admin/system.scala.html
@@ -66,6 +66,21 @@
+
+
+
@Html(file.highlightText)