Merge remote-tracking branch 'upstream/master' into anon-access

Conflicts:
	src/main/scala/app/SystemSettingsController.scala
	src/main/scala/service/SystemSettingsService.scala
	src/test/scala/view/AvatarImageProviderSpec.scala
This commit is contained in:
Mark LaCroix
2015-01-21 15:49:42 -05:00
27 changed files with 170 additions and 155 deletions

View File

@@ -8,6 +8,6 @@ Common scripts are in this directory.
This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat. This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat.
To run: To run:
1. Edit `gitbucket.conf` to suit.
2. Type: `install`
1. Edit `gitbucket.conf` to suit.
2. Type: `install`

View File

@@ -0,0 +1 @@
ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);

View File

@@ -291,7 +291,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
* Show the new repository form. * Show the new repository form.
*/ */
get("/new")(usersOnly { get("/new")(usersOnly {
account.html.newrepo(getGroupsByUserName(context.loginAccount.get.userName)) account.html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
}) })
/** /**

View File

@@ -248,10 +248,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val id = params("id") val id = params("id")
createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content, createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content,
form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined) form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
if (form.issueId.isDefined) form.issueId match {
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, form.issueId.get, form.content) case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
else case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content) }
redirect(s"/${repository.owner}/${repository.name}/commit/${id}") redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
}) })
@@ -273,10 +273,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val id = params("id") val id = params("id")
val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName,
form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined) form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
if (form.issueId.isDefined) form.issueId match {
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, form.issueId.get, form.content) case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
else case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content) }
helper.html.commitcomment(getCommitComment(repository.owner, repository.name, commentId.toString).get, helper.html.commitcomment(getCommitComment(repository.owner, repository.name, commentId.toString).get,
hasWritePermission(repository.owner, repository.name, context.loginAccount), repository) hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
}) })
@@ -532,7 +532,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
} }
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): File = { private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): Unit = {
val revision = name.stripSuffix(suffix) val revision = name.stripSuffix(suffix)
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId) val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
if(workDir.exists) { if(workDir.exists) {
@@ -540,21 +540,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
workDir.mkdirs workDir.mkdirs
val file = new File(workDir, repository.name + "-" + val filename = repository.name + "-" +
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix) (if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)) val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
using(new java.io.FileOutputStream(file)) { out =>
git.archive
.setFormat(suffix.tail)
.setTree(revCommit.getTree)
.setOutputStream(out)
.call()
}
contentType = "application/octet-stream" contentType = "application/octet-stream"
response.setHeader("Content-Disposition", s"attachment; filename=${file.getName}") response.setHeader("Content-Disposition", s"attachment; filename=${filename}")
file response.setBufferSize(1024 * 1024);
git.archive
.setFormat(suffix.tail)
.setTree(revCommit.getTree)
.setOutputStream(response.getOutputStream)
.call()
Unit
} }
} }

View File

@@ -17,6 +17,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
"information" -> trim(label("Information", optional(text()))), "information" -> trim(label("Information", optional(text()))),
"allowAccountRegistration" -> trim(label("Account registration", boolean())), "allowAccountRegistration" -> trim(label("Account registration", boolean())),
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())), "allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
"gravatar" -> trim(label("Gravatar", boolean())), "gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())), "notification" -> trim(label("Notification", boolean())),
"ssh" -> trim(label("SSH access", boolean())), "ssh" -> trim(label("SSH access", boolean())),
@@ -42,6 +43,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))), "fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
"mailAttribute" -> trim(label("Mail address attribute", optional(text()))), "mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
"tls" -> trim(label("Enable TLS", optional(boolean()))), "tls" -> trim(label("Enable TLS", optional(boolean()))),
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text()))) "keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply)) )(Ldap.apply))
)(SystemSettings.apply).verifying { settings => )(SystemSettings.apply).verifying { settings =>

View File

@@ -189,7 +189,7 @@ trait RepositoryService { self: AccountService =>
new RepositoryInfo( new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
repository, repository,
issues.size, issues.count(_ == false),
issues.count(_ == true), issues.count(_ == true),
getForkedCount( getForkedCount(
repository.originUserName.getOrElse(repository.userName), repository.originUserName.getOrElse(repository.userName),

View File

@@ -15,6 +15,7 @@ trait SystemSettingsService {
settings.information.foreach(x => props.setProperty(Information, x)) settings.information.foreach(x => props.setProperty(Information, x))
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString) props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString) props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
props.setProperty(Gravatar, settings.gravatar.toString) props.setProperty(Gravatar, settings.gravatar.toString)
props.setProperty(Notification, settings.notification.toString) props.setProperty(Notification, settings.notification.toString)
props.setProperty(Ssh, settings.ssh.toString) props.setProperty(Ssh, settings.ssh.toString)
@@ -43,6 +44,7 @@ trait SystemSettingsService {
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x)) ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x)) ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString)) ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x)) ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
} }
} }
@@ -65,6 +67,7 @@ trait SystemSettingsService {
getOptionValue[String](props, Information, None), getOptionValue[String](props, Information, None),
getValue(props, AllowAccountRegistration, false), getValue(props, AllowAccountRegistration, false),
getValue(props, AllowAnonymousAccess, true), getValue(props, AllowAnonymousAccess, true),
getValue(props, IsCreateRepoOptionPublic, true),
getValue(props, Gravatar, true), getValue(props, Gravatar, true),
getValue(props, Notification, false), getValue(props, Notification, false),
getValue(props, Ssh, false), getValue(props, Ssh, false),
@@ -94,6 +97,7 @@ trait SystemSettingsService {
getOptionValue(props, LdapFullNameAttribute, None), getOptionValue(props, LdapFullNameAttribute, None),
getOptionValue(props, LdapMailAddressAttribute, None), getOptionValue(props, LdapMailAddressAttribute, None),
getOptionValue[Boolean](props, LdapTls, None), getOptionValue[Boolean](props, LdapTls, None),
getOptionValue[Boolean](props, LdapSsl, None),
getOptionValue(props, LdapKeystore, None))) getOptionValue(props, LdapKeystore, None)))
} else { } else {
None None
@@ -112,6 +116,7 @@ object SystemSettingsService {
information: Option[String], information: Option[String],
allowAccountRegistration: Boolean, allowAccountRegistration: Boolean,
allowAnonymousAccess: Boolean, allowAnonymousAccess: Boolean,
isCreateRepoOptionPublic: Boolean,
gravatar: Boolean, gravatar: Boolean,
notification: Boolean, notification: Boolean,
ssh: Boolean, ssh: Boolean,
@@ -137,6 +142,7 @@ object SystemSettingsService {
fullNameAttribute: Option[String], fullNameAttribute: Option[String],
mailAttribute: Option[String], mailAttribute: Option[String],
tls: Option[Boolean], tls: Option[Boolean],
ssl: Option[Boolean],
keystore: Option[String]) keystore: Option[String])
case class Smtp( case class Smtp(
@@ -156,6 +162,7 @@ object SystemSettingsService {
private val Information = "information" private val Information = "information"
private val AllowAccountRegistration = "allow_account_registration" private val AllowAccountRegistration = "allow_account_registration"
private val AllowAnonymousAccess = "allow_anonymous_access" private val AllowAnonymousAccess = "allow_anonymous_access"
private val IsCreateRepoOptionPublic = "is_create_repository_option_public"
private val Gravatar = "gravatar" private val Gravatar = "gravatar"
private val Notification = "notification" private val Notification = "notification"
private val Ssh = "ssh" private val Ssh = "ssh"
@@ -178,6 +185,7 @@ object SystemSettingsService {
private val LdapFullNameAttribute = "ldap.fullname_attribute" private val LdapFullNameAttribute = "ldap.fullname_attribute"
private val LdapMailAddressAttribute = "ldap.mail_attribute" private val LdapMailAddressAttribute = "ldap.mail_attribute"
private val LdapTls = "ldap.tls" private val LdapTls = "ldap.tls"
private val LdapSsl = "ldap.ssl"
private val LdapKeystore = "ldap.keystore" private val LdapKeystore = "ldap.keystore"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =

View File

@@ -52,6 +52,7 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current BitBucket version. * The history of versions. A head of this sequence is the current BitBucket version.
*/ */
val versions = Seq( val versions = Seq(
new Version(2, 8),
new Version(2, 7) { new Version(2, 7) {
override def update(conn: Connection): Unit = { override def update(conn: Connection): Unit = {
super.update(conn) super.update(conn)

View File

@@ -48,6 +48,7 @@ object LDAPUtil {
dn = ldapSettings.bindDN.getOrElse(""), dn = ldapSettings.bindDN.getOrElse(""),
password = ldapSettings.bindPassword.getOrElse(""), password = ldapSettings.bindPassword.getOrElse(""),
tls = ldapSettings.tls.getOrElse(false), tls = ldapSettings.tls.getOrElse(false),
ssl = ldapSettings.ssl.getOrElse(false),
keystore = ldapSettings.keystore.getOrElse(""), keystore = ldapSettings.keystore.getOrElse(""),
error = "System LDAP authentication failed." error = "System LDAP authentication failed."
){ conn => ){ conn =>
@@ -65,6 +66,7 @@ object LDAPUtil {
dn = userDN, dn = userDN,
password = password, password = password,
tls = ldapSettings.tls.getOrElse(false), tls = ldapSettings.tls.getOrElse(false),
ssl = ldapSettings.ssl.getOrElse(false),
keystore = ldapSettings.keystore.getOrElse(""), keystore = ldapSettings.keystore.getOrElse(""),
error = "User LDAP Authentication Failed." error = "User LDAP Authentication Failed."
){ conn => ){ conn =>
@@ -96,7 +98,7 @@ object LDAPUtil {
}).replaceAll("[^a-zA-Z0-9\\-_.]", "").replaceAll("^[_\\-]", "") }).replaceAll("[^a-zA-Z0-9\\-_.]", "").replaceAll("^[_\\-]", "")
} }
private def bind[A](host: String, port: Int, dn: String, password: String, tls: Boolean, keystore: String, error: String) private def bind[A](host: String, port: Int, dn: String, password: String, tls: Boolean, ssl: Boolean, keystore: String, error: String)
(f: LDAPConnection => Either[String, A]): Either[String, A] = { (f: LDAPConnection => Either[String, A]): Either[String, A] = {
if (tls) { if (tls) {
// Dynamically set Sun as the security provider // Dynamically set Sun as the security provider
@@ -109,7 +111,13 @@ object LDAPUtil {
} }
} }
val conn: LDAPConnection = new LDAPConnection(new LDAPJSSEStartTLSFactory()) val conn: LDAPConnection =
if(ssl) {
new LDAPConnection(new LDAPJSSESecureSocketFactory())
}else {
new LDAPConnection(new LDAPJSSEStartTLSFactory())
}
try { try {
// Connect to the server // Connect to the server
conn.connect(host, port) conn.connect(host, port)

View File

@@ -18,9 +18,14 @@ object Markdown {
/** /**
* Converts Markdown of Wiki pages to HTML. * Converts Markdown of Wiki pages to HTML.
*/ */
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo, def toHtml(markdown: String,
enableWikiLink: Boolean, enableRefsLink: Boolean, repository: service.RepositoryService.RepositoryInfo,
enableTaskList: Boolean = false, hasWritePermission: Boolean = false)(implicit context: app.Context): String = { enableWikiLink: Boolean,
enableRefsLink: Boolean,
enableTaskList: Boolean = false,
hasWritePermission: Boolean = false,
pages: List[String] = Nil)(implicit context: app.Context): String = {
// escape issue id // escape issue id
val s = if(enableRefsLink){ val s = if(enableRefsLink){
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2") markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
@@ -35,12 +40,16 @@ object Markdown {
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML
).parseMarkdown(source.toCharArray) ).parseMarkdown(source.toCharArray)
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission).toHtml(rootNode) new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission, pages).toHtml(rootNode)
} }
} }
class GitBucketLinkRender(context: app.Context, repository: service.RepositoryService.RepositoryInfo, class GitBucketLinkRender(
enableWikiLink: Boolean) extends LinkRenderer with WikiService { context: app.Context,
repository: service.RepositoryService.RepositoryInfo,
enableWikiLink: Boolean,
pages: List[String]) extends LinkRenderer with WikiService {
override def render(node: WikiLinkNode): Rendering = { override def render(node: WikiLinkNode): Rendering = {
if(enableWikiLink){ if(enableWikiLink){
try { try {
@@ -54,7 +63,7 @@ class GitBucketLinkRender(context: app.Context, repository: service.RepositorySe
val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page)
if(getWikiPage(repository.owner, repository.name, page).isDefined){ if(pages.contains(page)){
new Rendering(url, label) new Rendering(url, label)
} else { } else {
new Rendering(url, label).withAttribute("class", "absent") new Rendering(url, label).withAttribute("class", "absent")
@@ -91,9 +100,10 @@ class GitBucketHtmlSerializer(
enableWikiLink: Boolean, enableWikiLink: Boolean,
enableRefsLink: Boolean, enableRefsLink: Boolean,
enableTaskList: Boolean, enableTaskList: Boolean,
hasWritePermission: Boolean hasWritePermission: Boolean,
pages: List[String]
)(implicit val context: app.Context) extends ToHtmlSerializer( )(implicit val context: app.Context) extends ToHtmlSerializer(
new GitBucketLinkRender(context, repository, enableWikiLink), new GitBucketLinkRender(context, repository, enableWikiLink, pages),
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
) with LinkConverter with RequestCache { ) with LinkConverter with RequestCache {

View File

@@ -86,9 +86,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
/** /**
* Converts Markdown of Wiki pages to HTML. * Converts Markdown of Wiki pages to HTML.
*/ */
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo, def markdown(value: String,
enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean = false, hasWritePermission: Boolean = false)(implicit context: app.Context): Html = repository: service.RepositoryService.RepositoryInfo,
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission)) enableWikiLink: Boolean,
enableRefsLink: Boolean,
enableTaskList: Boolean = false,
hasWritePermission: Boolean = false,
pages: List[String] = Nil)(implicit context: app.Context): Html =
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission, pages))
def renderMarkup(filePath: List[String], fileContent: String, branch: String, def renderMarkup(filePath: List[String], fileContent: String, branch: String,
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,

View File

@@ -1,4 +1,5 @@
@(groupNames: List[String])(implicit context: app.Context) @(groupNames: List[String],
isCreateRepoOptionPublic: Boolean)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main("Create a New Repository"){ @html.main("Create a New Repository"){
@@ -29,7 +30,7 @@
</fieldset> </fieldset>
<fieldset class="margin"> <fieldset class="margin">
<label class="radio"> <label class="radio">
<input type="radio" name="isPrivate" value="false" checked> <input type="radio" name="isPrivate" value="false" @if(isCreateRepoOptionPublic){checked}>
<span class="strong"><img src="@assets/common/images/repo_public.png"/>&nbsp;</i>&nbsp;Public</span><br> <span class="strong"><img src="@assets/common/images/repo_public.png"/>&nbsp;</i>&nbsp;Public</span><br>
<div> <div>
<span>All users and guests can read this repository.</span> <span>All users and guests can read this repository.</span>
@@ -38,7 +39,7 @@
</fieldset> </fieldset>
<fieldset> <fieldset>
<label class="radio"> <label class="radio">
<input type="radio" name="isPrivate" value="true"> <input type="radio" name="isPrivate" value="true" @if(!isCreateRepoOptionPublic){checked}>
<span class="strong"><img src="@assets/common/images/repo_private.png"/>&nbsp;</i>&nbsp;Private</span><br> <span class="strong"><img src="@assets/common/images/repo_private.png"/>&nbsp;</i>&nbsp;Private</span><br>
<div> <div>
<span>Only collaborators can read this repository.</span> <span>Only collaborators can read this repository.</span>

View File

@@ -53,6 +53,18 @@
<span class="strong">Deny</span> - Only administrators can create accounts. <span class="strong">Deny</span> - Only administrators can create accounts.
</label> </label>
</fieldset> </fieldset>
<hr>
<label class="strong">Default option to create a new repository</label>
<fieldset>
<label class="radio">
<input type="radio" name="isCreateRepoOptionPublic" value="true"@if(settings.isCreateRepoOptionPublic){ checked}>
<span class="strong">Public</span> - All users and guests can read that repository.
</label>
<label class="radio">
<input type="radio" name="isCreateRepoOptionPublic" value="false"@if(!settings.isCreateRepoOptionPublic){ checked}>
<span class="strong">Private</span> - Only collaborators can read that repository.
</label>
</fieldset>
<!--====================================================================--> <!--====================================================================-->
<!-- Anonymous access --> <!-- Anonymous access -->
<!--====================================================================--> <!--====================================================================-->
@@ -184,6 +196,13 @@
</label> </label>
</div> </div>
</div> </div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="ldap.ssl"@if(settings.ldap.flatMap(_.ssl).getOrElse(false)){ checked}/> Enable SSL
</label>
</div>
</div>
<div class="control-group"> <div class="control-group">
<label class="control-label" for="ldapBindDN">Keystore</label> <label class="control-label" for="ldapBindDN">Keystore</label>
<div class="controls"> <div class="controls">

View File

@@ -8,13 +8,6 @@
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import service.IssuesService.IssueInfo @import service.IssuesService.IssueInfo
@*
<ul class="nav nav-pills-group pull-left fill-width">
<li class="@if(filter == "created_by"){active} first"><a href="@path/dashboard/issues/created_by@condition.toURL">Created</a></li>
<li class="@if(filter == "assigned"){active}"><a href="@path/dashboard/issues/assigned@condition.toURL">Assigned</a></li>
<li class="@if(filter == "mentioned"){active} last"><a href="@path/dashboard/issues/mentioned@condition.toURL">Mentioned</a></li>
</ul>
*@
<table class="table table-bordered table-hover table-issues"> <table class="table table-bordered table-hover table-issues">
<tr> <tr>
<th style="background-color: #eee;"> <th style="background-color: #eee;">
@@ -29,7 +22,7 @@
} else { } else {
<img src="@assets/common/images/issue-@(if(issue.closed) "closed" else "open").png"/> <img src="@assets/common/images/issue-@(if(issue.closed) "closed" else "open").png"/>
} }
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a>&nbsp;&#xFF65; <a href="@path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65;
@if(issue.isPullRequest){ @if(issue.isPullRequest){
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a> <a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
} else { } else {

View File

@@ -11,6 +11,6 @@
@dashboard.html.tab("pulls") @dashboard.html.tab("pulls")
<div class="container"> <div class="container">
@issuesnavi(filter, "pulls", condition) @issuesnavi(filter, "pulls", condition)
@pullslist(issues, page, openCount, closedCount, condition, filter, groups) @issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div> </div>
} }

View File

@@ -1,67 +0,0 @@
@(issues: List[service.IssuesService.IssueInfo],
page: Int,
openCount: Int,
closedCount: Int,
condition: service.IssuesService.IssueSearchCondition,
filter: String,
groups: List[String])(implicit context: app.Context)
@import context._
@import view.helpers._
@import service.IssuesService.IssueInfo
@*
<ul class="nav nav-pills-group pull-left fill-width">
<li class="@if(filter == "created_by"){active} first"><a href="@path/dashboard/pulls/created_by@condition.toURL">Created</a></li>
<li class="@if(filter == "assigned"){active}"><a href="@path/dashboard/pulls/assigned@condition.toURL">Assigned</a></li>
<li class="@if(filter == "mentioned"){active} last"><a href="@path/dashboard/pulls/mentioned@condition.toURL">Mentioned</a></li>
<li class="pull-right">
<div class="input-prepend" style="margin-bottom: 0px;">
<div class="btn-group">
<button type="button" class="btn dropdown-toggle" data-toggle="dropdown" style="height: 34px;">
Filter
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="?q=is:open">Open issues and pull requests</a></li>
<li><a href="?q=is:open+is:issue+author:@urlEncode(loginAccount.get.userName)">Your issues</a></li>
<li><a href="?q=is:open+is:pr+author:@urlEncode(loginAccount.get.userName)">Your pull requests</a></li>
<li><a href="?q=is:open+assignee:@urlEncode(loginAccount.get.userName)">Everything assigned to you</a></li>
<li><a href="?q=is:open+mentions:@urlEncode(loginAccount.get.userName)">Everything mentioning you</a></li>
</ul>
</div>
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px;" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
</div>
</li>
</ul>
*@
<table class="table table-bordered table-hover table-issues">
<tr>
<th style="background-color: #eee;">
@dashboard.html.header(openCount, closedCount, condition, groups)
</th>
</tr>
@issues.map { case IssueInfo(issue, labels, milestone, commentCount) =>
<tr>
<td>
<img src="@assets/common/images/pullreq-@(if(issue.closed) "closed" else "open").png"/>
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
<span class="pull-right muted">#@issue.issueId</span>
<div style="margin-left: 20px;">
@issue.content.map { content =>
@cut(content, 90)
}.getOrElse {
<span class="muted">No description available</span>
}
</div>
<div class="small muted" style="margin-left: 20px;">
@avatarLink(issue.openedUserName, 20) by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)&nbsp;
@if(commentCount > 0){
<i class="icon-comment"></i><a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">@commentCount @plural(commentCount, "comment")</a>
}
</div>
</td>
</tr>
}
</table>
<div class="pull-right">
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), service.PullRequestService.PullRequestLimit, 10, condition.toURL)
</div>

View File

@@ -120,6 +120,13 @@ $(function(){
renderDiffs(0); renderDiffs(0);
}); });
$('.toggle-notes').change(function() {
if (!$(this).prop('checked')) {
$(this).closest('table').find('.not-diff.inline-comment-form').remove();
}
$(this).closest('table').find('.not-diff').toggle();
});
function renderDiffs(viewType){ function renderDiffs(viewType){
window.viewType = viewType; window.viewType = viewType;
@diffs.zipWithIndex.map { case (diff, i) => @diffs.zipWithIndex.map { case (diff, i) =>
@@ -146,33 +153,31 @@ $(function(){
if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) { if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) {
$(this).hide(); $(this).hide();
} }
var tmp;
var diff;
if (typeof oldline !== 'undefined') { if (typeof oldline !== 'undefined') {
var tmp;
if (typeof newline !== 'undefined') { if (typeof newline !== 'undefined') {
tmp = getInlineContainer(); tmp = getInlineContainer();
} else { } else {
tmp = getInlineContainer('old'); tmp = getInlineContainer('old');
} }
tmp.children('td:first').html($(this).clone().show()); tmp.children('td:first').html($(this).clone().show());
$('table[filename="' + filename + '"]').find('table.diff').find('.oldline').filter(function() { diff = $('table[filename="' + filename + '"]');
return new RegExp('^' + oldline + '[\\+]*$').test($(this).text()); diff.find('table.diff').find('.oldline[line-number=' + oldline + ']')
}).parent().nextAll(':not(.not-diff):first').before(tmp); .parent().nextAll(':not(.not-diff):first').before(tmp);
} else { } else {
var tmp = getInlineContainer('new'); tmp = getInlineContainer('new');
tmp.children('td:last').html($(this).clone().show()); tmp.children('td:last').html($(this).clone().show());
$('table[filename="' + filename + '"]').find('table.diff').find('.newline').filter(function() { diff = $('table[filename="' + filename + '"]');
return new RegExp('^' + newline + '\\+$').test($(this).text()); diff.find('table.diff').find('.newline[line-number=' + newline + ']')
}).parent().nextAll(':not(.not-diff):first').before(tmp); .parent().nextAll(':not(.not-diff):first').before(tmp);
} }
}); if (!diff.find('.toggle-notes').prop('checked')) {
$('.toggle-notes').change(function() { tmp.hide();
if (!$(this).prop('checked')) {
$(this).closest('table').find('.not-diff.inline-comment-form').remove();
} }
$(this).closest('table').find('.not-diff').toggle();
}); });
@if(hasWritePermission) { @if(hasWritePermission) {
$('table.diff th').hover( $('table.diff td').hover(
function() { function() {
$(this).find('b').css('display', 'inline-block'); $(this).find('b').css('display', 'inline-block');
}, },
@@ -180,12 +185,12 @@ $(function(){
$(this).find('b').css('display', 'none'); $(this).find('b').css('display', 'none');
} }
); );
$('table.diff td').hover( $('table.diff th').hover(
function() { function() {
$(this).prev('th').find('b').css('display', 'inline-block'); $(this).nextAll().find('b').first().css('display', 'inline-block');
}, },
function() { function() {
$(this).prev('th').find('b').css('display', 'none'); $(this).nextAll().find('b').first().css('display', 'none');
} }
); );
$('.add-comment').click(function() { $('.add-comment').click(function() {
@@ -198,12 +203,14 @@ $(function(){
if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) { if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) {
var commitId = $this.closest('.table-bordered').attr('commitId'), var commitId = $this.closest('.table-bordered').attr('commitId'),
fileName = $this.closest('.table-bordered').attr('fileName'), fileName = $this.closest('.table-bordered').attr('fileName'),
oldLineNumber, newLineNumber = $this.closest('.newline').clone().children().remove().end().text(), oldLineNumber, newLineNumber,
url = '@url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName @if(issueId.isDefined){+ '&issueId=@issueId.get'}; url = '@url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
if (viewType == 0) { if (viewType == 0) {
oldLineNumber = $this.closest('.oldline').clone().children().remove().end().text(); oldLineNumber = $this.parent().prev('.oldline').attr('line-number');
newLineNumber = $this.parent().prev('.newline').attr('line-number');
} else { } else {
oldLineNumber = $this.closest('tr').find('.oldline').text(); oldLineNumber = $this.parent().prevAll('.oldline').attr('line-number');
newLineNumber = $this.parent().prevAll('.newline').attr('line-number');
} }
if (!isNaN(oldLineNumber) && oldLineNumber) { if (!isNaN(oldLineNumber) && oldLineNumber) {
url += ('&oldLineNumber=' + oldLineNumber) url += ('&oldLineNumber=' + oldLineNumber)

View File

@@ -43,7 +43,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="brand" href="@path/"> <a class="brand" href="@path/">
<img src="@assets/common/images/gitbucket.png"/>GitBucket <img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket
@defining(servlet.AutoUpdate.getCurrentVersion){ version => @defining(servlet.AutoUpdate.getCurrentVersion){ version =>
<span class="header-version">@version.majorVersion.@version.minorVersion</span> <span class="header-version">@version.majorVersion.@version.minorVersion</span>
} }

View File

@@ -62,7 +62,7 @@
<p> <p>
<span class="strong">Step 3:</span> Merge the changes and update the server <span class="strong">Step 3:</span> Merge the changes and update the server
</p> </p>
@defining(s"git checkout master\ngit merge ${pullreq.requestUserName}-${pullreq.requestBranch}\ngit push origin ${pullreq.branch}"){ command => @defining(s"git checkout ${pullreq.branch}\ngit merge ${pullreq.requestUserName}-${pullreq.requestBranch}\ngit push origin ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-3", command){ @helper.html.copy("merge-command-copy-3", command){
<pre style="width: 500px; float: left;">@command</pre> <pre style="width: 500px; float: left;">@command</pre>
} }

View File

@@ -29,10 +29,10 @@
<input type="submit" class="btn btn-success" formaction="@url(repository)/commit/@commitId/comment/new" value="Comment on this commit"/> <input type="submit" class="btn btn-success" formaction="@url(repository)/commit/@commitId/comment/new" value="Comment on this commit"/>
</div> </div>
} }
@if(issueId.isDefined){<input type="hidden" name="issueId" value="@issueId.get">} @issueId.map { issueId => <input type="hidden" name="issueId" value="@issueId"> }
@if(fileName.isDefined){<input type="hidden" name="fileName" value="@fileName.get">} @fileName.map { fileName => <input type="hidden" name="fileName" value="@fileName"> }
@if(oldLineNumber.isDefined){<input type="hidden" name="oldLineNumber" value="@oldLineNumber.get">} @oldLineNumber.map { oldLineNumber => <input type="hidden" name="oldLineNumber" value="@oldLineNumber"> }
@if(newLineNumber.isDefined){<input type="hidden" name="newLineNumber" value="@newLineNumber.get">} @newLineNumber.map { newLineNumber => <input type="hidden" name="newLineNumber" value="@newLineNumber"> }
</form> </form>
<script> <script>
$('.btn-inline-comment').click(function(e) { $('.btn-inline-comment').click(function(e) {

View File

@@ -2,6 +2,12 @@
@import context._ @import context._
@main("Sign in"){ @main("Sign in"){
<div class="signin-form"> <div class="signin-form">
@settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
}
@signinform(settings) @signinform(settings)
</div> </div>
} }

View File

@@ -53,7 +53,7 @@
</div> </div>
<div style="width: 650px;" class="pull-left"> <div style="width: 650px;" class="pull-left">
<div class="markdown-body"> <div class="markdown-body">
@markdown(page.content, repository, true, false) @markdown(page.content, repository, true, false, false, false, pages)
</div> </div>
</div> </div>
} }

View File

@@ -975,15 +975,16 @@ table.diff .add-comment {
position: absolute; position: absolute;
background: blue; background: blue;
top: 0; top: 0;
left: -7px;
color: white; color: white;
padding: 2px; padding: 2px 4px;
border: solid 1px blue; border: solid 1px blue;
border-radius: 3px; border-radius: 3px;
z-index: 99; z-index: 99;
} }
table.diff .add-comment:hover { table.diff .add-comment:hover {
padding: 4px; padding: 4px 6px;
top: -1px; top: -1px;
} }
@@ -1009,6 +1010,14 @@ table.diff tbody tr.not-diff:hover td{
padding: 10px; padding: 10px;
} }
.diff .oldline:before, .diff .newline:before {
content: attr(line-number);
}
.diff .skipline:before {
content: "..."
}
/****************************************************************************/ /****************************************************************************/
/* Repository Settings */ /* Repository Settings */
/****************************************************************************/ /****************************************************************************/
@@ -1088,6 +1097,8 @@ div.markdown-body pre {
div.markdown-body code { div.markdown-body code {
font-size: 12px; font-size: 12px;
padding: 0 5px; padding: 0 5px;
background-color: rgba(0,0,0,0.04);
rgb(51, 51, 51);
} }
div.markdown-body table { div.markdown-body table {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -84,7 +84,7 @@ diffview = {
b.appendChild(document.createTextNode("+")); b.appendChild(document.createTextNode("+"));
b.style.display = "none"; b.style.display = "none";
b.className = "add-comment"; b.className = "add-comment";
e.appendChild(b); e.insertBefore(b, e.firstChild);
e.style.position = "relative"; e.style.position = "relative";
return e; return e;
} }
@@ -117,9 +117,12 @@ diffview = {
* to the given row. * to the given row.
*/ */
function addCells (row, tidx, tend, textLines, change, thclass) { function addCells (row, tidx, tend, textLines, change, thclass) {
var tmp;
if (tidx < tend) { if (tidx < tend) {
row.appendChild(addButton(ctelt("th", thclass, (tidx + 1).toString()))); tmp = ctelt("th", thclass, "");
row.appendChild(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); tmp.setAttribute("line-number", (tidx + 1).toString());
row.appendChild(tmp);
row.appendChild(addButton(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))));
return tidx + 1; return tidx + 1;
} else { } else {
row.appendChild(document.createElement("th")); row.appendChild(document.createElement("th"));
@@ -129,9 +132,13 @@ diffview = {
} }
function addCellsInline (row, tidx, tidx2, textLines, change) { function addCellsInline (row, tidx, tidx2, textLines, change) {
row.appendChild(ctelt("th", "oldline", tidx == null ? "" : (tidx + 1).toString())); var tmp = ctelt("th", "oldline", "");
row.appendChild(addButton(ctelt("th", "newline", tidx2 == null ? "" : (tidx2 + 1).toString()))); tmp.setAttribute("line-number", tidx == null ? "" : (tidx + 1).toString());
row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))); row.appendChild(tmp);
tmp = ctelt("th", "newline", "");
tmp.setAttribute("line-number", tidx2 == null ? "" : (tidx2 + 1).toString());
row.appendChild(tmp);
row.appendChild(addButton(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0"))));
} }
for (var idx = 0; idx < opcodes.length; idx++) { for (var idx = 0; idx < opcodes.length; idx++) {
@@ -154,9 +161,9 @@ diffview = {
b += jump; b += jump;
n += jump; n += jump;
i += jump - 1; i += jump - 1;
node.appendChild(telt("th", "...")); node.appendChild(ctelt("th", "skipline", ""));
if (!inline) node.appendChild(ctelt("td", "skip", "")); if (!inline) node.appendChild(ctelt("td", "skip", ""));
node.appendChild(telt("th", "...")); node.appendChild(ctelt("th", "skipline", ""));
node.appendChild(ctelt("td", "skip", "")); node.appendChild(ctelt("td", "skip", ""));
// skip last lines if they're all equal // skip last lines if they're all equal

View File

@@ -96,6 +96,7 @@ class AvatarImageProviderSpec extends Specification with Mockito {
information = None, information = None,
allowAccountRegistration = false, allowAccountRegistration = false,
allowAnonymousAccess = true, allowAnonymousAccess = true,
isCreateRepoOptionPublic = true,
gravatar = useGravatar, gravatar = useGravatar,
notification = false, notification = false,
ssh = false, ssh = false,