Merge branch 'pr-add-release'

This commit is contained in:
Naoki Takezoe
2018-01-13 16:31:45 +09:00
20 changed files with 646 additions and 39 deletions

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<createTable tableName="RELEASE">
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="RELEASE_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="NAME" type="varchar(100)" nullable="false"/>
<column name="TAG" type="varchar(100)" nullable="false"/>
<column name="AUTHOR" type="varchar(100)" nullable="false"/>
<column name="CONTENT" type="text" nullable="true"/>
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_RELEASE_PK" tableName="RELEASE" columnNames="USER_NAME, REPOSITORY_NAME, RELEASE_ID"/>
<addUniqueConstraint constraintName="IDX_RELEASE_UNIQ" tableName="RELEASE" columnNames="USER_NAME, REPOSITORY_NAME, TAG"/>
<addForeignKeyConstraint constraintName="IDX_RELEASE_FK0" baseTableName="RELEASE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
<createTable tableName="RELEASE_ASSET">
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
<column name="RELEASE_ID" type="int" nullable="false"/>
<column name="RELEASE_ASSET_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
<column name="FILE_NAME" type="varchar(260)" nullable="false"/>
<column name="LABEL" type="varchar(100)" nullable="true"/>
<column name="SIZE" type="bigint" nullable="false"/>
<column name="UPLOADER" type="varchar(100)" nullable="false"/>
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
</createTable>
<addPrimaryKey constraintName="IDX_RELEASE_ASSET_PK" tableName="RELEASE_ASSET" columnNames="USER_NAME, REPOSITORY_NAME, RELEASE_ID, FILE_NAME"
/>
<addForeignKeyConstraint constraintName="IDX_RELEASE_ASSET_FK1" baseTableName="RELEASE_ASSET" baseColumnNames="USER_NAME, REPOSITORY_NAME, RELEASE_ID" referencedTableName="RELEASE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, RELEASE_ID"/>
</changeSet>

View File

@@ -2,7 +2,7 @@
import java.util.EnumSet
import javax.servlet._
import gitbucket.core.controller._
import gitbucket.core.controller.{ReleaseController, _}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.servlet._
@@ -47,6 +47,7 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
filter.mount(new MilestonesController, "/*")
filter.mount(new IssuesController, "/*")
filter.mount(new PullRequestsController, "/*")
filter.mount(new ReleaseController, "/*")
filter.mount(new RepositorySettingsController, "/*")
context.addFilter("compositeScalatraFilter", filter)

View File

@@ -48,5 +48,8 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new Version("4.19.1"),
new Version("4.19.2"),
new Version("4.19.3"),
new Version("4.20.0")
new Version("4.20.0"),
new Version("4.21.0",
new LiquibaseMigration("update/gitbucket-core_4.21.xml")
)
)

View File

@@ -1,7 +1,7 @@
package gitbucket.core.controller
import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.service.{AccountService, RepositoryService, ReleaseService}
import gitbucket.core.servlet.Database
import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
@@ -19,7 +19,11 @@ import org.apache.commons.io.{FileUtils, IOUtils}
*
* This servlet saves uploaded file.
*/
class FileUploadController extends ScalatraServlet with FileUploadSupport with RepositoryService with AccountService {
class FileUploadController extends ScalatraServlet
with FileUploadSupport
with RepositoryService
with AccountService
with ReleaseService{
configureMultipartHandling(MultipartConfig(maxFileSize = Some(FileUtil.MaxFileSize)))
@@ -84,6 +88,20 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
} getOrElse BadRequest()
}
post("/release/:owner/:repository/:tag"){
session.get(Keys.Session.LoginAccount).collect { case _: Account =>
val owner = params("owner")
val repository = params("repository")
val tag = params("tag")
execute({ (file, fileId) =>
FileUtils.writeByteArrayToFile(
new java.io.File(getReleaseFilesDir(owner, repository), tag + "/" + fileId),
file.get
)
}, _ => true)
}.getOrElse(BadRequest())
}
post("/import") {
import JDBCUtil._
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>

View File

@@ -0,0 +1,140 @@
package gitbucket.core.controller
import java.io.File
import gitbucket.core.service.{AccountService, ActivityService, ReleaseService, RepositoryService}
import gitbucket.core.util.{FileUtil, ReadableUsersAuthenticator, ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import org.scalatra.forms._
import gitbucket.core.releases.html
import org.apache.commons.io.FileUtils
import scala.collection.JavaConverters._
class ReleaseController extends ReleaseControllerBase
with RepositoryService
with AccountService
with ReleaseService
with ActivityService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
trait ReleaseControllerBase extends ControllerBase {
self: RepositoryService
with AccountService
with ReleaseService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
with ActivityService =>
case class ReleaseForm(
name: String,
content: Option[String]
)
val releaseForm = mapping(
"name" -> trim(text(required)),
"content" -> trim(optional(text()))
)(ReleaseForm.apply)
get("/:owner/:repository/releases")(referrersOnly {repository =>
val releases = getReleases(repository.owner, repository.name)
val assets = getReleaseAssetsMap(repository.owner, repository.name)
html.list(
repository,
repository.tags.reverse.map { tag =>
(tag, releases.find(_.tag == tag.name).map { release => (release, assets(release)) })
},
hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
})
get("/:owner/:repository/releases/:id")(referrersOnly {repository =>
val id = params("id")
getRelease(repository.owner, repository.name, id).map{ release =>
html.release(release, getReleaseAssets(repository.owner, repository.name, id), hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
}.getOrElse(NotFound())
})
get("/:owner/:repository/releases/:id/assets/:fileId")(referrersOnly {repository =>
val releaseId = params("id")
val fileId = params("fileId")
(for {
release <- getRelease(repository.owner, repository.name, releaseId)
asset <- getReleaseAsset(repository.owner, repository.name, releaseId, fileId)
} yield {
response.setHeader("Content-Disposition", s"attachment; filename=${asset.label}")
Some(RawData(
FileUtil.getMimeType(asset.label),
new File(getReleaseFilesDir(repository.owner, repository.name), release.tag + "/" + fileId)
))
}).getOrElse(NotFound())
})
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly {repository =>
html.form(repository, params("tag"), None)
})
post("/:owner/:repository/releases/:tag/create", releaseForm)(writableUsersOnly { (form, repository) =>
val tag = params("tag")
val loginAccount = context.loginAccount.get
// Insert into RELEASE
val release = createRelease(repository.owner, repository.name, form.name, form.content, tag, loginAccount)
// Insert into RELEASE_ASSET
request.getParameterNames.asScala.filter(_.startsWith("file:")).foreach { paramName =>
val Array(_, fileId) = paramName.split(":")
val fileName = params(paramName)
val size = new java.io.File(getReleaseFilesDir(repository.owner, repository.name), tag + "/" + fileId).length
createReleaseAsset(repository.owner, repository.name, release.releaseId, fileId, fileName, size, loginAccount)
}
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, release.releaseId, release.name)
redirect(s"/${release.userName}/${release.repositoryName}/releases/${release.releaseId}")
})
get("/:owner/:repository/releases/:id/edit")(writableUsersOnly {repository =>
val releaseId = params("id").toInt
getRelease(repository.owner, repository.name, releaseId).map { release =>
html.form(repository, release.tag, Some(release, getReleaseAssets(repository.owner, repository.name, releaseId)))
}.getOrElse(NotFound())
})
post("/:owner/:repository/releases/:id/edit", releaseForm)(writableUsersOnly { (form, repository) =>
val releaseId = params("id").toInt
val loginAccount = context.loginAccount.get
getRelease(repository.owner, repository.name, releaseId).map { release =>
// Update RELEASE
updateRelease(repository.owner, repository.name, releaseId, form.name, form.content)
// Delete and Insert RELEASE_ASSET
deleteReleaseAssets(repository.owner, repository.name, releaseId)
request.getParameterNames.asScala.filter(_.startsWith("file:")).foreach { paramName =>
val Array(_, fileId) = paramName.split(":")
val fileName = params(paramName)
val size = new java.io.File(getReleaseFilesDir(repository.owner, repository.name), release.tag + "/" + fileId).length
createReleaseAsset(repository.owner, repository.name, release.releaseId, fileId, fileName, size, loginAccount)
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${release.releaseId}")
}.getOrElse(NotFound())
})
post("/:owner/:repository/releases/:id/delete")(writableUsersOnly { repository =>
val releaseId = params("id")
getRelease(repository.owner, repository.name, releaseId).foreach { release =>
FileUtils.deleteDirectory(new File(getReleaseFilesDir(repository.owner, repository.name), release.tag))
}
deleteRelease(repository.owner, repository.name, releaseId)
redirect(s"/${repository.owner}/${repository.name}/releases")
})
}

View File

@@ -142,6 +142,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
FileUtils.moveDirectory(dir, getRepositoryFilesDir(repository.owner, form.repositoryName))
}
}
// Delete parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
// Call hooks
PluginRegistry().getRepositoryHooks.foreach(_.renamed(repository.owner, repository.name, form.repositoryName))
}

View File

@@ -627,8 +627,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
/**
* Displays tags.
*/
get("/:owner/:repository/tags")(referrersOnly {
html.tags(_)
get("/:owner/:repository/tags")(referrersOnly { repository =>
redirect(s"${repository.owner}/${repository.name}/releases")
})
/**

View File

@@ -63,5 +63,7 @@ trait CoreProfile extends ProfileProvider with Profile
with AccountWebHookEventComponent
with ProtectedBranchComponent
with DeployKeyComponent
with ReleaseComponent
with ReleaseAssetComponent
object Profile extends CoreProfile

View File

@@ -0,0 +1,49 @@
package gitbucket.core.model
trait ReleaseComponent extends TemplateComponent {
self: Profile =>
import profile.api._
import self._
lazy val Releases = TableQuery[Releases]
class Releases(tag_ : Tag) extends Table[Release](tag_, "RELEASE") with BasicTemplate {
val releaseId = column[Int]("RELEASE_ID", O AutoInc)
val name = column[String]("NAME")
val tag = column[String]("TAG")
val author = column[String]("AUTHOR")
val content = column[Option[String]]("CONTENT")
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = (userName, repositoryName, releaseId, name, tag, author, content, registeredDate, updatedDate) <> (Release.tupled, Release.unapply)
def byPrimaryKey(owner: String, repository: String, releaseId: Int) = byRelease(owner, repository, releaseId)
def byTag(owner: String, repository: String, tag: String) =
byRepository(owner, repository) && (this.tag === tag.bind)
def byTag(userName: Rep[String], repositoryName: Rep[String], tag: Rep[String]) =
byRepository(userName, repositoryName) && (this.tag === tag)
def byRelease(owner: String, repository: String, releaseId: Int) =
byRepository(owner, repository) && (this.releaseId === releaseId.bind)
def byRelease(userName: Rep[String], repositoryName: Rep[String], releaseId: Rep[Int]) =
byRepository(userName, repositoryName) && (this.releaseId === releaseId)
}
}
case class Release(
userName: String,
repositoryName: String,
releaseId: Int = 0,
name: String,
tag: String,
author: String,
content: Option[String],
registeredDate: java.util.Date,
updatedDate: java.util.Date
)

View File

@@ -0,0 +1,47 @@
package gitbucket.core.model
import java.util.Date
trait ReleaseAssetComponent extends TemplateComponent {
self: Profile =>
import profile.api._
import self._
lazy val ReleaseAssets = TableQuery[ReleaseAssets]
class ReleaseAssets(tag : Tag) extends Table[ReleaseAsset](tag, "RELEASE_ASSET") with BasicTemplate {
val releaseId = column[Int]("RELEASE_ID")
val releaseAssetId = column[Int]("RELEASE_ASSET_ID", O AutoInc)
val fileName = column[String]("FILE_NAME")
val label = column[String]("LABEL")
val size = column[Long]("SIZE")
val uploader = column[String]("UPLOADER")
val registeredDate = column[Date]("REGISTERED_DATE")
val updatedDate = column[Date]("UPDATED_DATE")
def * = (userName, repositoryName, releaseId, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate) <> (ReleaseAsset.tupled, ReleaseAsset.unapply)
def byPrimaryKey(owner: String, repository: String, releaseId: Int, fileName: String) = byRelease(owner, repository, releaseId) && (this.fileName === fileName.bind)
def byRelease(owner: String, repository: String, releaseId: Int) =
byRepository(owner, repository) && (this.releaseId === releaseId.bind)
def byRelease(userName: Rep[String], repositoryName: Rep[String], releaseId: Rep[Int]) =
byRepository(userName, repositoryName) && (this.releaseId === releaseId)
}
}
case class ReleaseAsset(
userName: String,
repositoryName: String,
releaseId: Int,
releaseAssetId: Int = 0,
fileName: String,
label: String,
size: Long,
uploader: String,
registeredDate: Date,
updatedDate: Date
)

View File

@@ -190,6 +190,13 @@ trait ActivityService {
Some(message),
currentDate)
def recordReleaseActivity(userName: String, repositoryName: String, activityUserName: String, releaseId: Int, name: String)(implicit s: Session): Unit =
Activities insert Activity(userName, repositoryName, activityUserName,
"release",
s"[user:${activityUserName}] released ${name} at [repo:${userName}/${repositoryName}]",
None,
currentDate)
private def cut(value: String, length: Int): String =
if(value.length > length) value.substring(0, length) + "..." else value
}

View File

@@ -0,0 +1,100 @@
package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.model.{Account, Release, ReleaseAsset}
import gitbucket.core.util.StringUtil._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.dateColumnType
trait ReleaseService {
self: AccountService with RepositoryService =>
def createReleaseAsset(owner: String, repository: String, releaseId: Int, fileName: String, label: String, size: Long, loginAccount: Account)(implicit s: Session): Unit = {
ReleaseAssets insert ReleaseAsset(
userName = owner,
repositoryName = repository,
releaseId = releaseId,
fileName = fileName,
label = label,
size = size,
uploader = loginAccount.userName,
registeredDate = currentDate,
updatedDate = currentDate
)
}
def getReleaseAssets(owner: String, repository: String, releaseId: Int)(implicit s: Session): Seq[ReleaseAsset] = {
ReleaseAssets.filter(x => x.byRelease(owner, repository, releaseId)).list
}
def getReleaseAssets(owner: String, repository: String, releaseId: String)(implicit s: Session): Seq[ReleaseAsset] = {
if (isInteger(releaseId))
getReleaseAssets(owner, repository, releaseId.toInt)
else
Seq.empty
}
def getReleaseAssetsMap(owner: String, repository: String)(implicit s: Session): Map[Release, Seq[ReleaseAsset]] = {
val releases = getReleases(owner, repository)
releases.map(rel => (rel -> getReleaseAssets(owner, repository, rel.releaseId))).toMap
}
def getReleaseAsset(owner: String, repository: String, releaseId: String, fileId: String)(implicit s: Session): Option[ReleaseAsset] = {
if (isInteger(releaseId))
ReleaseAssets.filter(x => x.byPrimaryKey(owner, repository, releaseId.toInt, fileId)) firstOption
else None
}
def deleteReleaseAssets(owner: String, repository: String, releaseId: Int)(implicit s: Session): Unit = {
ReleaseAssets.filter(x => x.byRelease(owner, repository, releaseId)) delete
}
def createRelease(owner: String, repository: String, name: String, content: Option[String], tag: String,
loginAccount: Account)(implicit context: Context, s: Session): Release = {
Releases insert Release(
userName = owner,
repositoryName = repository,
name = name,
tag = tag,
author = loginAccount.userName,
content = content,
registeredDate = currentDate,
updatedDate = currentDate
)
getReleaseByTag(owner, repository, tag).get
}
def getReleases(owner: String, repository: String)(implicit s: Session): Seq[Release] = {
Releases.filter(x => x.byRepository(owner, repository)).list
}
def getRelease(owner: String, repository: String, releaseId: Int)(implicit s: Session): Option[Release] = {
Releases filter (_.byPrimaryKey(owner, repository, releaseId)) firstOption
}
def getReleaseByTag(owner: String, repository: String, tag: String)(implicit s: Session): Option[Release] = {
Releases filter (_.byTag(owner, repository, tag)) firstOption
}
def getRelease(owner: String, repository: String, releaseId: String)(implicit s: Session): Option[Release] = {
if (isInteger(releaseId))
getRelease(owner, repository, releaseId.toInt)
else None
}
def updateRelease(owner: String, repository: String, releaseId: Int, title: String, content: Option[String])(implicit s: Session): Int = {
Releases
.filter (_.byPrimaryKey(owner, repository, releaseId))
.map { t => (t.name, t.content, t.updatedDate) }
.update (title, content, currentDate)
}
def deleteRelease(owner: String, repository: String, releaseId: String)(implicit s: Session): Unit = {
if (isInteger(releaseId)){
val relId = releaseId.toInt
deleteReleaseAssets(owner, repository, relId)
Releases filter (_.byPrimaryKey(owner, repository, relId)) delete
}
}
}

View File

@@ -3,7 +3,7 @@ package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.model.{Account, Collaborator, Repository, RepositoryOptions, Role}
import gitbucket.core.model.{Account, Collaborator, Repository, RepositoryOptions, Role, Release}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
@@ -75,6 +75,8 @@ trait RepositoryService { self: AccountService =>
val protectedBranches = ProtectedBranches .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val protectedBranchContexts = ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list
val deployKeys = DeployKeys .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val releases = Releases .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val releaseAssets = ReleaseAssets .filter(_.byRepository(oldUserName, oldRepositoryName)).list
Repositories.filter { t =>
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
@@ -120,6 +122,8 @@ trait RepositoryService { self: AccountService =>
ProtectedBranches .insertAll(protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
ProtectedBranchContexts.insertAll(protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
DeployKeys .insertAll(deployKeys .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Releases .insertAll(releases .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
ReleaseAssets .insertAll(releaseAssets .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
// Update source repository of pull requests
PullRequests.filter { t =>
@@ -173,6 +177,8 @@ trait RepositoryService { self: AccountService =>
RepositoryWebHooks .filter(_.byRepository(userName, repositoryName)).delete
RepositoryWebHookEvents .filter(_.byRepository(userName, repositoryName)).delete
DeployKeys .filter(_.byRepository(userName, repositoryName)).delete
ReleaseAssets .filter(_.byRepository(userName, repositoryName)).delete
Releases .filter(_.byRepository(userName, repositoryName)).delete
Repositories .filter(_.byRepository(userName, repositoryName)).delete
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME
@@ -514,7 +520,7 @@ object RepositoryService {
this(repo.owner, repo.name, model, issueCount, pullCount, forkedCount, repo.branchList, repo.tags, managers)
/**
* Creates instance without issue count and pull request count.
* Creates instance without issue and pull request count.
*/
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) =
this(repo.owner, repo.name, model, 0, 0, forkedCount, repo.branchList, repo.tags, managers)
@@ -531,6 +537,10 @@ object RepositoryService {
(id, path.substring(id.length).stripPrefix("/"))
}
// def getReleaseByTag(tag: String)(implicit s: Session): Option[Release] = {
// Releases filter (_.byTag(owner, name, tag)) firstOption
// }
}
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
@@ -539,5 +549,4 @@ object RepositoryService {
context.settings.sshAddress.map { x => s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" }
} else None
def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}"
}

View File

@@ -54,6 +54,12 @@ object Directory {
def getAttachedDir(owner: String, repository: String): File =
new File(getRepositoryFilesDir(owner, repository), "comments")
/**
* Directory for released files
*/
def getReleaseFilesDir(owner: String, repository: String): File =
new File(getRepositoryFilesDir(owner, repository), "releases")
/**
* Directory for files which are attached to issue.
*/

View File

@@ -14,6 +14,7 @@
case "reopen_issue" => detailActivity(activity, "issue-reopened")
case "open_pullreq" => detailActivity(activity, "git-pull-request")
case "merge_pullreq" => detailActivity(activity, "git-merge")
case "release" => detailActivity(activity, "package")
case "create_repository" => simpleActivity(activity, "repo")
case "create_branch" => simpleActivity(activity, "git-branch")
case "delete_branch" => simpleActivity(activity, "circle-slash")

View File

@@ -33,8 +33,8 @@
@menuitem("", "files", "Files", "code")
@if(repository.branchList.nonEmpty) {
@menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length)
@menuitem("/tags", "tags", "Tags", "tag", repository.tags.length)
}
@menuitem("/releases", "releases", "Releases", "tag", repository.tags.length)
@if(repository.repository.options.issuesOption != "DISABLE") {
@menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount)
@menuitem("/pulls", "pulls", "Pull requests", "git-pull-request", repository.pullCount)

View File

@@ -0,0 +1,87 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
tag: String,
release: Option[(gitbucket.core.model.Release, Seq[gitbucket.core.model.ReleaseAsset])])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@action = {
@release.map { case (release, _) =>
@helpers.url(repository)/releases/@release.releaseId/edit
}.getOrElse {
@helpers.url(repository)/releases/@helpers.encodeRefName(tag)/create
}
}
@gitbucket.core.html.main(s"New Release - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("releases", repository){
<form action="@action.toString.trim" method="POST" validate="true" class="form-group">
<div class="row-fluid">
<div class="co`l-md-12">
@if(release.isEmpty){
<h3>New release for @tag</h3>
} else {
<h3>Update release for @tag</h3>
}
<span id="error-name" class="error"></span>
<input type="text" id="release-name" name="name" class="form-control" value="@release.map { case (release, _) => @release.name }.getOrElse(tag)" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
@gitbucket.core.helper.html.preview(
repository = repository,
content = release.flatMap { case (release, _) => release.content }.getOrElse(""),
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true,
completionContext = "releases",
style = "height: 200px; max-height: 500px;",
elastic = true,
placeholder = "Describe this release"
)
<ul id="assets-list" class="collaborator">
@release.map { case (release, assets) =>
@assets.map { asset =>
<li>
<a href="@context.baseUrl/@repository.owner/@repository.name/_release/@helpers.encodeRefName(tag)/@asset.fileName"><i class="octicon octicon-file"></i>@asset.label</a>
<a href="#" class="remove pull-right" style="padding-top: 0px;">(remove)</a>
<input type="hidden" name="file:@asset.fileName" value="@asset.label"/>
</li>
}
}
</ul>
<div style="border: 1px dashed #ccc; color: gray; background-color: #eee; padding: 4px;">
<div id="drop" class="clickable">Attach release files by dragging &amp; dropping, or selecting them.</div>
</div>
<div class="align-right" style="margin-top: 12px;">
@if(release.isEmpty){
<input type="submit" class="btn btn-success" value="Submit new release"/>
} else {
<input type="submit" class="btn btn-success" value="Update release"/>
}
</div>
</div>
</div>
</form>
}
}
<script>
$(function(){
$(document).on('click', '.remove', function(){
$(this).parent().remove();
});
$("#drop").dropzone({
maxFilesize: @{gitbucket.core.util.FileUtil.MaxFileSize / 1024 / 1024},
url: '@context.path/upload/release/@repository.owner/@repository.name/@helpers.encodeRefName(tag)',
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var attach =
'<li><a href="@context.baseUrl/@repository.owner/@repository.name/_release/@helpers.encodeRefName(tag)/' + id + '">' +
'<i class="octicon octicon-file"></i>' + file.name + '</a>' +
'<a href="#" class="remove pull-right" style="padding-top: 0px;">(remove)</a>' +
'<input type="hidden" name="file:' + id + '" value="' + file.name + '"/>' + // TODO escape file.name
'</li>';
$('#assets-list').append(attach);
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
}
});
});
</script>

View File

@@ -0,0 +1,64 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
releases: Seq[(gitbucket.core.util.JGitUtil.TagInfo, Option[(gitbucket.core.model.Release, Seq[gitbucket.core.model.ReleaseAsset])])],
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Releases" + s" - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("releases", repository){
<table class="table table-bordered table-releases">
<thead>
<tr><th>@releases.length releases</th></tr>
</thead>
<tbody>
@releases.map { case (tag, release) =>
<tr>
<td>
<div class="col-md-2 text-right">
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(tag.name)" class="strong"><i class="octicon octicon-tag"></i>@tag.name</a><br>
<a href="@helpers.url(repository)/commit/@tag.id" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.id.substring(0, 7)</a><br>
<span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span>
</div>
<div class="col-md-10" style="border-left: 1px solid #eee">
<div class="release-note markdown-body">
@release.map { case (release, assets) =>
<h3><a href="@helpers.url(repository)/releases/@release.releaseId">@release.name</a></h3>
<p class="muted">
@helpers.avatar(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
</p>
@helpers.markdown(
markdown = release.content getOrElse "No description provided.",
repository = repository,
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = hasWritePermission
)
}.getOrElse {
@if(hasWritePermission){
<div class="pull-right">
<a class="btn btn-success" href="@helpers.url(repository)/releases/@{helpers.encodeRefName(tag.name)}/create" id="edit">Create release</a>
</div>
}
}
<h4>Downloads</h4>
<ul style="list-style: none; padding-left: 8px;">
@release.map { case (release, assets) =>
@assets.map { asset =>
<li>
<i class="octicon octicon-file"></i><a href="@helpers.url(repository)/releases/@release.releaseId/assets/@asset.fileName">@asset.label</a>
<span class="label label-default">@helpers.readableSize(Some(asset.size))</span>
</li>
}
}
<li><a href="@helpers.url(repository)/archive/@{helpers.encodeRefName(tag.name)}.zip"><i class="octicon octicon-file-zip"></i>Source code (zip)</a></li>
<li><a href="@helpers.url(repository)/archive/@{helpers.encodeRefName(tag.name)}.tar.gz"><i class="octicon octicon-file-zip"></i>Source code (tar.gz)</a></li>
</ul>
</div>
</div>
</td>
</tr>
}
</tbody>
</table>
}
}

View File

@@ -0,0 +1,65 @@
@(release: gitbucket.core.model.Release,
assets: Seq[gitbucket.core.model.ReleaseAsset],
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"${release.name} - Release ${release.releaseId} - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("releases", repository){
<div class="row">
<div class="col-md-2 text-right">
@defining(repository.tags.find(_.name == release.tag)){ tag =>
@tag.map { tag =>
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(tag.name)" class="strong"><i class="octicon octicon-tag"></i>@tag.name</a><br>
<a href="@helpers.url(repository)/commit/@tag.id" class="monospace muted"><i class="octicon octicon-git-commit"></i>@tag.id.substring(0, 7)</a><br>
<span class="muted">@gitbucket.core.helper.html.datetimeago(tag.time)</span>
}
}
</div>
<div class="col-md-10" style="border-left: 1px solid #eee">
<div class="markdown-body">
<h3>
@release.name
@if(hasWritePermission){
<div class="pull-right">
<form method="POST" action="@helpers.url(repository)/releases/@release.releaseId/delete" id="delete-form">
<a class="btn btn-default" href="@helpers.url(repository)/releases/@release.releaseId/edit" id="edit">Edit</a>
<input type="submit" class="btn btn-danger" value="Delete">
</form>
</div>
}
</h3>
<p class="muted">
@helpers.avatar(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
</p>
@helpers.markdown(
markdown = release.content getOrElse "No description provided.",
repository = repository,
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = hasWritePermission
)
<h4>Downloads</h4>
<ul style="list-style: none; padding-left: 8px;" id="attachedFiles">
@assets.map{ asset =>
<li>
<i class="octicon octicon-file"></i><a href="@helpers.url(repository)/releases/@release.releaseId/assets/@asset.fileName">@asset.label</a>
<span class="label label-default">@helpers.readableSize(Some(asset.size))</span>
</li>
}
<li><a href="@helpers.url(repository)/archive/@{helpers.encodeRefName(release.tag)}.zip"><i class="octicon octicon-file-zip"></i>Source code (zip)</a></li>
<li><a href="@helpers.url(repository)/archive/@{helpers.encodeRefName(release.tag)}.tar.gz"><i class="octicon octicon-file-zip"></i>Source code (tar.gz)</a></li>
</ul>
</div>
</div>
</div>
}
}
<script>
$(function(){
$('#delete-form').submit(function(){
return confirm('Are you sure you want to delete this release?');
});
});
</script>

View File

@@ -1,29 +0,0 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@gitbucket.core.html.menu("tags", repository){
<table class="table table-bordered">
<thead>
<tr>
<th width="40%">Tag</th>
<th width="20%">Date</th>
<th width="20%">Commit</th>
<th width="20%">Download</th>
</tr>
</thead>
<tbody>
@repository.tags.reverseMap { tag =>
<tr>
<td><a href="@helpers.url(repository)/tree/@helpers.encodeRefName(tag.name)">@tag.name</a></td>
<td>@gitbucket.core.helper.html.datetimeago(tag.time, false)</td>
<td class="monospace"><a href="@helpers.url(repository)/commit/@tag.id">@tag.id.substring(0, 10)</a></td>
<td>
<a href="@helpers.url(repository)/archive/@{helpers.encodeRefName(tag.name)}.zip">ZIP</a>
<a href="@helpers.url(repository)/archive/@{helpers.encodeRefName(tag.name)}.tar.gz">TAR.GZ</a>
</td>
</tr>
}
</tbody>
</table>
}
}