Compare commits

...

19 Commits

Author SHA1 Message Date
Naoki Takezoe
baf560d532 Release GitBucket 4.46.1 (#4008) 2026-04-18 11:09:43 +09:00
gitbucket-bot[bot]
448b3ac144 Update sbt-scalafmt to 2.6.0 2026-04-14 03:46:50 +09:00
gitbucket-bot[bot]
82be26dbae Update scalafmt-core to 3.11.0 2026-04-13 11:20:16 +09:00
gitbucket-bot[bot]
102c9e8245 Update sbt, sbt-dependency-tree, ... to 1.12.9 2026-04-08 03:49:49 +09:00
Shreejan Shrestha
2587f2bd19 Improve pull request compare performance by making mergeability and diff loading manually triggerable, with configurable default mode (#3996) 2026-04-05 20:40:14 +09:00
gitbucket-bot[bot]
9c4c2dfc81 Update scala3-library to 3.8.3 2026-04-02 06:38:11 +09:00
Naoki Takezoe
6ff90aae1a Fix NullPointerException in the commits page (#4000) 2026-04-01 09:57:52 +09:00
gitbucket-bot[bot]
79a9cfb3bb Update oauth2-oidc-sdk to 11.37 2026-03-30 03:41:39 +09:00
gitbucket-bot[bot]
c218cf70b6 Update oauth2-oidc-sdk to 11.36 2026-03-29 18:22:14 +09:00
gitbucket-bot[bot]
4b4810438e Update sbt, sbt-dependency-tree, ... to 1.12.8 2026-03-25 06:59:55 +09:00
gitbucket-bot[bot]
371913a9d5 Update oauth2-oidc-sdk to 11.35 2026-03-24 18:57:20 +09:00
gitbucket-bot[bot]
3d13ae705a Update sbt, sbt-dependency-tree, ... to 1.12.7 2026-03-24 17:49:37 +09:00
gitbucket-bot[bot]
abd94807fe Update tika-core to 3.3.0 2026-03-24 07:39:04 +09:00
gitbucket-bot[bot]
eea43c30d2 Update testcontainers-mysql, ... to 2.0.4 (#3991)
Co-authored-by: gitbucket-bot[bot] <256891351+gitbucket-bot[bot]@users.noreply.github.com>
2026-03-20 02:59:44 +09:00
gitbucket-bot[bot]
32bf51ff1a Update commons-net to 3.13.0 (#3990)
Co-authored-by: gitbucket-bot[bot] <256891351+gitbucket-bot[bot]@users.noreply.github.com>
2026-03-20 02:59:19 +09:00
gitbucket-bot[bot]
e32c68c7d7 Update sbt, sbt-dependency-tree, ... to 1.12.6 2026-03-17 06:56:34 +09:00
gitbucket-bot[bot]
c6691e1766 Update mockito-core to 5.23.0 2026-03-13 05:29:09 +09:00
gitbucket-bot[bot]
860813174a Update oauth2-oidc-sdk to 11.34 (#3983)
Co-authored-by: gitbucket-bot[bot] <256891351+gitbucket-bot[bot]@users.noreply.github.com>
2026-03-08 02:47:35 +09:00
Naoki Takezoe
d10736387b Suppress JGitUtil's debug logs in test (#3982) 2026-03-07 12:50:48 +09:00
22 changed files with 244 additions and 109 deletions

View File

@@ -1,4 +1,4 @@
version = "3.10.7"
version = "3.11.0"
project.git = true
maxColumn = 120

View File

@@ -1,6 +1,10 @@
# Changelog
All changes to the project will be documented in this file.
## 4.46.1 - 18 Apr 2026
- Fix NullPointerException that could happen in the commits page
- Add options to improve pull request compare performance
## 4.46.0 - 7 Mar 2026
- Add support for reverting pull request
- Add markdown toolbar
@@ -90,7 +94,7 @@ db {
## 4.38.1 - 10 Sep 2022
- Fix comment diff in Chrome 105
- Fix Markdown table CSS
- Fix HTML rendering of multiple asignees
- Fix HTML rendering of multiple assignees
## 4.38.0 - 3 Sep 2022
- Support multiple assignees for Issues and Pull requests

View File

@@ -59,8 +59,12 @@ Support
- If you can't find same question and report, send it to our [Gitter chat room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
What's New in 4.45.x
What's New in 4.46.x
-------------
## 4.46.1 - 18 Apr 2026
- Fix NullPointerException that could happen in the commits page
- Add options to improve pull request compare performance
## 4.46.0 - 7 Mar 2026
- Add support for reverting pull request
- Add markdown toolbar

View File

@@ -2,7 +2,7 @@ import com.jsuereth.sbtpgp.PgpKeys._
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.46.0"
val GitBucketVersion = "4.46.1"
val ScalatraVersion = "3.1.2"
val JettyVersion = "10.0.26"
val JgitVersion = "6.10.1.202505221210-r"
@@ -16,7 +16,7 @@ name := Name
version := GitBucketVersion
scalaVersion := "2.13.18"
crossScalaVersions += "3.8.2"
crossScalaVersions += "3.8.3"
// scalafmtOnCompile := true
@@ -35,14 +35,14 @@ libraryDependencies ++= Seq(
"org.tukaani" % "xz" % "1.12",
"org.apache.commons" % "commons-compress" % "1.28.0",
"org.apache.commons" % "commons-email" % "1.6.0",
"commons-net" % "commons-net" % "3.12.0",
"commons-net" % "commons-net" % "3.13.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
"org.apache.sshd" % "apache-sshd" % "2.17.1" exclude ("org.slf4j", "slf4j-jdk14") exclude (
"org.apache.sshd",
"sshd-mina"
) exclude ("org.apache.sshd", "sshd-netty")
exclude ("org.apache.sshd", "sshd-spring-sftp"),
"org.apache.tika" % "tika-core" % "3.2.3",
"org.apache.tika" % "tika-core" % "3.3.0",
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "2.4.240",
@@ -57,14 +57,14 @@ libraryDependencies ++= Seq(
"org.cache2k" % "cache2k-core" % "2.6.1.Final",
"net.coobird" % "thumbnailator" % "0.4.21",
"com.github.zafarkhaja" % "java-semver" % "0.10.2",
"com.nimbusds" % "oauth2-oidc-sdk" % "11.33",
"com.nimbusds" % "oauth2-oidc-sdk" % "11.37",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.13.2" % "test",
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "5.22.0" % "test",
"org.testcontainers" % "testcontainers-mysql" % "2.0.3" % "test",
"org.testcontainers" % "testcontainers-postgresql" % "2.0.3" % "test",
"org.mockito" % "mockito-core" % "5.23.0" % "test",
"org.testcontainers" % "testcontainers-mysql" % "2.0.4" % "test",
"org.testcontainers" % "testcontainers-postgresql" % "2.0.4" % "test",
"net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "1.2.0",

View File

@@ -1 +1 @@
sbt.version=1.12.5
sbt.version=1.12.9

View File

@@ -1,6 +1,6 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.6")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.0")
addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "2.0.9")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")

View File

@@ -123,7 +123,8 @@ object GitBucketCoreModule
new Version("4.43.0"),
new Version("4.44.0", new LiquibaseMigration("update/gitbucket-core_4.44.xml")),
new Version("4.45.0"),
new Version("4.46.0", new LiquibaseMigration("update/gitbucket-core_4.46.xml"))
new Version("4.46.0", new LiquibaseMigration("update/gitbucket-core_4.46.xml")),
new Version("4.46.1")
) {
java.util.logging.Logger.getLogger("liquibase").setLevel(Level.SEVERE)
}

View File

@@ -375,6 +375,11 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
val headBranch = params.get("head")
val quickLoad = params
.get("quick")
.map(_.equalsIgnoreCase("true"))
.getOrElse(context.settings.basicBehavior.compareNoCheckByDefault)
val quickQuery = if (quickLoad) "?quick=true" else ""
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) =>
getRepository(originUserName, originRepositoryName).map { originRepository =>
@@ -388,7 +393,7 @@ trait PullRequestsControllerBase extends ControllerBase {
.getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2)
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/$originUserName:$oldBranch...$newBranch"
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}${quickQuery}"
)
}
} getOrElse NotFound()
@@ -396,7 +401,7 @@ trait PullRequestsControllerBase extends ControllerBase {
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/$defaultBranch...${headBranch.getOrElse(defaultBranch)}"
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}${quickQuery}"
)
} getOrElse {
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
@@ -436,76 +441,112 @@ trait PullRequestsControllerBase extends ControllerBase {
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
val requestedCheck = params.get("check").contains("true")
val quickLoad = params
.get("quick")
.map(_.equalsIgnoreCase("true"))
.getOrElse(!requestedCheck && context.settings.basicBehavior.compareNoCheckByDefault)
(for (
originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
val members =
((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(userName), Some(repositoryName)) =>
getRepository(userName, repositoryName) match {
case Some(x) => x.repository :: getForkedRepositories(userName, repositoryName)
case None => getForkedRepositories(userName, repositoryName)
}
case _ =>
forkedRepository.repository :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
}).map { repository =>
(repository.userName, repository.repositoryName, repository.defaultBranch)
}
(oldId, newId) match {
case (Some(oldId), Some(newId)) =>
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner,
originRepository.name,
oldId.getName,
forkedRepository.owner,
forkedRepository.name,
newId.getName,
context.settings
)
val text = forkedId.replaceAll("[\\-_]", " ")
val fallbackTitle = text.substring(0, 1).toUpperCase + text.substring(1)
val title = if (commits.flatten.length == 1) {
commits.flatten.head.shortMessage
} else {
val text = forkedId.replaceAll("[\\-_]", " ")
text.substring(0, 1).toUpperCase + text.substring(1)
}
if (quickLoad) {
html.compare(
fallbackTitle,
Seq.empty,
Seq.empty,
members,
List.empty,
originId,
forkedId,
"",
"",
getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"),
forkedRepository,
originRepository,
forkedRepository,
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
getAssignableUserNames(originRepository.owner, originRepository.name),
getMilestones(originRepository.owner, originRepository.name),
getPriorities(originRepository.owner, originRepository.name),
getDefaultPriority(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name),
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests),
quickLoad
)
} else {
val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
html.compare(
title,
commits,
diffs,
((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(userName), Some(repositoryName)) =>
getRepository(userName, repositoryName) match {
case Some(x) => x.repository :: getForkedRepositories(userName, repositoryName)
case None => getForkedRepositories(userName, repositoryName)
}
case _ =>
forkedRepository.repository :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
}).map { repository =>
(repository.userName, repository.repositoryName, repository.defaultBranch)
},
commits.flatten
.flatMap(commit =>
getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, includePullRequest = false)
)
.toList,
originId,
forkedId,
oldId.getName,
newId.getName,
getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"),
forkedRepository,
originRepository,
forkedRepository,
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
getAssignableUserNames(originRepository.owner, originRepository.name),
getMilestones(originRepository.owner, originRepository.name),
getPriorities(originRepository.owner, originRepository.name),
getDefaultPriority(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name),
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests)
)
case (oldId, newId) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
s"$originOwner:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
s"$forkedOwner:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
)
(oldId, newId) match {
case (Some(oldId), Some(newId)) =>
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner,
originRepository.name,
oldId.getName,
forkedRepository.owner,
forkedRepository.name,
newId.getName,
context.settings
)
val title = if (commits.flatten.length == 1) {
commits.flatten.head.shortMessage
} else {
fallbackTitle
}
val commitComments = commits.flatten
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
.toList
html.compare(
title,
commits,
diffs,
members,
commitComments,
originId,
forkedId,
oldId.getName,
newId.getName,
getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"),
forkedRepository,
originRepository,
forkedRepository,
hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
getAssignableUserNames(originRepository.owner, originRepository.name),
getMilestones(originRepository.owner, originRepository.name),
getPriorities(originRepository.owner, originRepository.name),
getDefaultPriority(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name),
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests),
quickLoad
)
case (oldId, newId) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
)
}
}
}) getOrElse NotFound()
})

View File

@@ -49,6 +49,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
"compareNoCheckByDefault" -> trim(label("Default compare mode", boolean())),
)(BasicBehavior.apply),
"ssh" -> mapping(
"enabled" -> trim(label("SSH access", boolean())),

View File

@@ -30,6 +30,7 @@ trait SystemSettingsService {
props.setProperty(Gravatar, settings.basicBehavior.gravatar.toString)
props.setProperty(Notification, settings.basicBehavior.notification.toString)
props.setProperty(LimitVisibleRepositories, settings.basicBehavior.limitVisibleRepositories.toString)
props.setProperty(CompareNoCheckByDefault, settings.basicBehavior.compareNoCheckByDefault.toString)
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
settings.ssh.bindAddress.foreach { bindAddress =>
props.setProperty(SshBindAddressHost, bindAddress.host.trim())
@@ -128,7 +129,8 @@ trait SystemSettingsService {
),
getValue(props, Gravatar, false),
getValue(props, Notification, false),
getValue(props, LimitVisibleRepositories, false)
getValue(props, LimitVisibleRepositories, false),
getValue(props, CompareNoCheckByDefault, false)
),
Ssh(
enabled = getValue(props, SshEnabled, false),
@@ -281,6 +283,7 @@ object SystemSettingsService {
gravatar: Boolean,
notification: Boolean,
limitVisibleRepositories: Boolean,
compareNoCheckByDefault: Boolean,
)
case class RepositoryOperation(
@@ -413,6 +416,7 @@ object SystemSettingsService {
private val Gravatar = "gravatar"
private val Notification = "notification"
private val LimitVisibleRepositories = "limitVisibleRepositories"
private val CompareNoCheckByDefault = "compare_no_check_by_default"
private val SshEnabled = "ssh"
private val SshHost = "ssh.host"
private val SshPort = "ssh.port"

View File

@@ -638,7 +638,7 @@ object JGitUtil {
count: Int,
logs: List[CommitInfo]
): (List[CommitInfo], Boolean) =
if (i.hasNext && limit <= 0 || logs.size < limit) {
if (i.hasNext && (limit <= 0 || logs.size < limit)) {
val commit = i.next
getCommitLog(
i,

View File

@@ -240,6 +240,21 @@
</label>
</fieldset>
<!--====================================================================-->
<!-- Compare default mode -->
<!--====================================================================-->
<hr>
<label class="strong">Pull request compare default mode</label>
<fieldset>
<label class="radio">
<input type="radio" name="basicBehavior.compareNoCheckByDefault" value="true"@if(context.settings.basicBehavior.compareNoCheckByDefault){ checked}>
<span class="strong">No check (fast)</span> <span class="normal">- Open compare quickly and run checks only when clicking Check mergeability.</span>
</label>
<label class="radio">
<input type="radio" name="basicBehavior.compareNoCheckByDefault" value="false"@if(!context.settings.basicBehavior.compareNoCheckByDefault){ checked}>
<span class="strong">Check by default</span> <span class="normal">- Load full compare and mergeability behavior by default.</span>
</label>
</fieldset>
<!--====================================================================-->
<!-- Show mail address -->
<!--====================================================================-->
<hr>

View File

@@ -37,7 +37,7 @@
<a class="btn btn-success" href="@helpers.url(repository)/issues/new">New issue</a>
}
@if(target == "pulls"){
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
<a class="btn btn-success" href="@helpers.url(repository)/compare@if(context.settings.basicBehavior.compareNoCheckByDefault){?quick=true}">New pull request</a>
}
}
</form>

View File

@@ -197,7 +197,7 @@
@if(target == "issues"){
<a href="@helpers.url(repository.get)/issues/new">Create a new issue.</a>
} else {
<a href="@helpers.url(repository.get)/compare">Create a new pull request.</a>
<a href="@helpers.url(repository.get)/compare@if(context.settings.basicBehavior.compareNoCheckByDefault){?quick=true}">Create a new pull request.</a>
}
}
@*

View File

@@ -17,11 +17,11 @@
priorities: List[gitbucket.core.model.Priority],
defaultPriority: Option[gitbucket.core.model.Priority],
labels: List[gitbucket.core.model.Label],
customFields: List[gitbucket.core.model.CustomField])(implicit context: gitbucket.core.controller.Context)
customFields: List[gitbucket.core.model.CustomField],
quickLoad: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"Pull requests - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("pulls", repository){
<form method="POST" action="@context.path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
<div class="pullreq-info">
<div id="compare-edit">
@gitbucket.core.helper.html.dropdown(originRepository.owner + "/" + originRepository.name, "base fork", filter=("origin_repo", "Find Repository...")) {
@@ -54,14 +54,25 @@
}
<span class="error" id="error-requestBranch"></span>
</div>
@if(originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId)){
<div class="check-conflict" style="display: none;">
<img src="@helpers.assets("/common/images/indicator.gif")"/> Checking...
</div>
}
</div>
@if(commits.nonEmpty && context.loginAccount.isDefined && originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId)){
<div id="pull-request-form" style="margin-bottom: 20px;">
@if(originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId)){
<div style="margin-top: 8px;">
<button type="button" class="btn btn-primary btn-sm" id="check-conflict-button">Check mergeability</button>
</div>
<div class="check-conflict" style="display: none;">
<img src="@helpers.assets("/common/images/indicator.gif")"/> Checking...
</div>
}
<div id="compare-state"
data-quick-load="@quickLoad"
data-has-valid-branches="@(originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId))"
data-default-no-check="@context.settings.basicBehavior.compareNoCheckByDefault"
data-can-auto-mergecheck="@(context.loginAccount.isDefined && originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId))"
style="display: none;"></div>
</div>
@if(commits.nonEmpty && context.loginAccount.isDefined && originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId)){
<div id="pull-request-form" style="margin-bottom: 20px;">
<form method="POST" action="@context.path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
<div class="row">
<div class="col-md-9">
<span class="error" id="error-title"></span>
@@ -115,10 +126,15 @@
)
</div>
</div>
</div>
}
</form>
@if(commits.isEmpty){
</form>
</div>
}
@if(quickLoad){
<div class="panel panel-default" style="padding: 20px; background-color: #eee; text-align: center;">
<h4>Comparison is ready to load.</h4>
<span class="muted">Click <span class="strong">Check mergeability</span> to compute changes and show merge status.</span>
</div>
} else if(commits.isEmpty){
<div class="panel panel-default" style="padding: 20px; background-color: #eee; text-align: center;">
<h4>There isn't anything to compare.</h4>
<span class="strong">@originRepository.owner:@originId</span> and <span class="strong">@forkedRepository.owner:@forkedId</span> are identical.
@@ -194,6 +210,17 @@ $(function(){
$(function(){
var compareState = $('#compare-state');
var isQuickLoad = compareState.data('quick-load') === true || compareState.data('quick-load') === 'true';
var defaultNoCheck = compareState.data('default-no-check') === true || compareState.data('default-no-check') === 'true';
var autoMergecheck =
/(?:\?|&)check=true(?:&|$)/.test(window.location.search) || (!isQuickLoad && !defaultNoCheck);
var hasValidBranches =
compareState.data('has-valid-branches') === true || compareState.data('has-valid-branches') === 'true';
var canAutoMergecheck =
compareState.data('can-auto-mergecheck') === true || compareState.data('can-auto-mergecheck') === 'true';
var compareQuery = (defaultNoCheck || isQuickLoad) ? '?quick=true' : '';
function updateSelector(e){
e.parents('ul').find('i').attr('class', 'octicon');
e.find('i').addClass('octicon-check');
@@ -209,7 +236,7 @@ $(function(){
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('default-branch')) + '...' +
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch')) + compareQuery;
});
$('a.forked-owner').click(function(){
@@ -221,7 +248,7 @@ $(function(){
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('default-branch'));
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('default-branch')) + compareQuery;
});
$('a.origin-branch, a.forked-branch').click(function(){
@@ -233,22 +260,44 @@ $(function(){
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'));
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch')) + compareQuery;
});
@if(context.loginAccount.isDefined && originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId)){
if (isQuickLoad && hasValidBranches) {
$('#check-conflict-button').click(function(){
location.href = '@helpers.url(forkedRepository)/compare/' +
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ':' +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch')) + '?check=true';
});
} else if (canAutoMergecheck) {
function checkConflict(from, to){
var button = $('#check-conflict-button');
button.attr('disabled', 'disabled');
$('.check-conflict').show();
$('.check-conflict').html('<img src="' + '@helpers.assets("/common/images/indicator.gif")' + '"/> Checking...');
$.get('@helpers.url(forkedRepository)/compare/' + from + '...' + to + '/mergecheck',
function(data){ $('.check-conflict').html(data); });
function(data){ $('.check-conflict').html(data); }
).fail(function(){
$('.check-conflict').html('<span class="strong" style="color: #bd2c00;">Failed to check mergeability.</span>');
}).always(function(){
button.removeAttr('disabled');
});
}
checkConflict(
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ":" +
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')),
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ":" +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'))
);
$('#check-conflict-button').click(function(){
checkConflict(
$.trim($('i.octicon-check').parents('a.origin-owner' ).data('owner')) + ":" +
$.trim($('i.octicon-check').parents('a.origin-branch').data('branch')),
$.trim($('i.octicon-check').parents('a.forked-owner' ).data('owner')) + ":" +
$.trim($('i.octicon-check').parents('a.forked-branch').data('branch'))
);
});
if (autoMergecheck) {
$('#check-conflict-button').click();
}
}
});
</script>

View File

@@ -19,7 +19,7 @@
<a class="btn btn-default" href="#" id="edit">Edit</a>
}
@if(context.loginAccount.isDefined) {
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
<a class="btn btn-success" href="@helpers.url(repository)/compare@if(context.settings.basicBehavior.compareNoCheckByDefault){?quick=true}">New pull request</a>
}
</div>
<div class="edit-title pull-right" style="display: none;">

View File

@@ -7,7 +7,7 @@
<div class="box-content" style="line-height: 20pt; margin-bottom: 6px; padding: 10px 6px 10px 10px; background-color: #fff9ea">
<strong><i class="menu-icon octicon octicon-git-branch"></i><span class="muted">@branch</span></strong>
<a class="pull-right btn btn-success" style="position: relative; top: -4px;"
href="@helpers.url(repository)/compare/@{parent.owner}:@{helpers.encodeRefName(parent.repository.defaultBranch)}...@{repository.owner}:@{helpers.encodeRefName(branch)}">Compare & pull request</a>
href="@helpers.url(repository)/compare/@{parent.owner}:@{helpers.encodeRefName(parent.repository.defaultBranch)}...@{repository.owner}:@{helpers.encodeRefName(branch)}@if(context.settings.basicBehavior.compareNoCheckByDefault){?quick=true}">Compare & pull request</a>
</div>
}
}

View File

@@ -64,7 +64,7 @@
helpers.urlEncode(parent) + ":" + helpers.encodeRefName(repository.repository.defaultBranch)
}.getOrElse {
helpers.encodeRefName(repository.repository.defaultBranch)
}}...@{helpers.encodeRefName(branch.name)}" class="btn btn-default btn-sm">@if(context.loginAccount.isDefined){Create pull request} else {Compare}</a>
}}...@{helpers.encodeRefName(branch.name)}@if(context.settings.basicBehavior.compareNoCheckByDefault){?quick=true}" class="btn btn-default btn-sm">@if(context.loginAccount.isDefined){Create pull request} else {Compare}</a>
}
@if(hasWritePermission){
<span style="margin-left: 8px;">

View File

@@ -16,6 +16,7 @@
-->
<logger name="gitbucket" level="DEBUG"/>
<logger name="gitbucket.core.util.JGitUtil" level="WARN"/>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>

View File

@@ -55,6 +55,7 @@ trait ServiceSpecBase {
gravatar = false,
notification = false,
limitVisibleRepositories = false,
compareNoCheckByDefault = false,
),
ssh = Ssh(
enabled = false,

View File

@@ -68,6 +68,19 @@ class SystemSettingsServiceSpec extends AnyWordSpecLike with Matchers {
settings.ssh.bindAddress shouldNot be(empty)
settings.ssh.publicAddress shouldNot be(empty)
}
"default compare_no_check_by_default to false if not specified" in new SystemSettingsService {
val props = new Properties()
val settings = loadSystemSettings(props)
settings.basicBehavior.compareNoCheckByDefault shouldBe false
}
"read compare_no_check_by_default configuration when true" in new SystemSettingsService {
val props = new Properties()
props.setProperty("compare_no_check_by_default", "true")
val settings = loadSystemSettings(props)
settings.basicBehavior.compareNoCheckByDefault shouldBe true
}
}
"SshAddress" can {

View File

@@ -165,7 +165,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
),
gravatar = useGravatar,
notification = false,
limitVisibleRepositories = false
limitVisibleRepositories = false,
compareNoCheckByDefault = false
),
ssh = Ssh(
enabled = false,