diff --git a/src/main/resources/update/gitbucket-core_4.21.xml b/src/main/resources/update/gitbucket-core_4.21.xml
new file mode 100644
index 0000000..842389c
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.21.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala
index 231da65..08458b5 100644
--- a/src/main/scala/ScalatraBootstrap.scala
+++ b/src/main/scala/ScalatraBootstrap.scala
@@ -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 @@
filter.mount(new MilestonesController, "/*")
filter.mount(new IssuesController, "/*")
filter.mount(new PullRequestsController, "/*")
+ filter.mount(new ReleaseController, "/*")
filter.mount(new RepositorySettingsController, "/*")
context.addFilter("compositeScalatraFilter", filter)
diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
index b61153a..29bc472 100644
--- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
+++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
@@ -48,5 +48,8 @@
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")
+ )
)
diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala
index 5bb0ce1..ca00ee2 100644
--- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala
+++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala
@@ -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 @@
*
* 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 @@
} 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 =>
diff --git a/src/main/scala/gitbucket/core/controller/ReleasesController.scala b/src/main/scala/gitbucket/core/controller/ReleasesController.scala
new file mode 100644
index 0000000..c1cdebf
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/ReleasesController.scala
@@ -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")
+ })
+
+}
diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
index f6c0bb4..b1e5b3d 100644
--- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
@@ -142,6 +142,9 @@
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))
}
diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
index 48cc2d3..3a3b3b0 100644
--- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
@@ -627,8 +627,8 @@
/**
* Displays tags.
*/
- get("/:owner/:repository/tags")(referrersOnly {
- html.tags(_)
+ get("/:owner/:repository/tags")(referrersOnly { repository =>
+ redirect(s"${repository.owner}/${repository.name}/releases")
})
/**
diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala
index 807456b..a70af57 100644
--- a/src/main/scala/gitbucket/core/model/Profile.scala
+++ b/src/main/scala/gitbucket/core/model/Profile.scala
@@ -63,5 +63,7 @@
with AccountWebHookEventComponent
with ProtectedBranchComponent
with DeployKeyComponent
+ with ReleaseComponent
+ with ReleaseAssetComponent
object Profile extends CoreProfile
diff --git a/src/main/scala/gitbucket/core/model/Release.scala b/src/main/scala/gitbucket/core/model/Release.scala
new file mode 100644
index 0000000..27fdbc0
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/Release.scala
@@ -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
+)
diff --git a/src/main/scala/gitbucket/core/model/ReleasesAsset.scala b/src/main/scala/gitbucket/core/model/ReleasesAsset.scala
new file mode 100644
index 0000000..156ef75
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/ReleasesAsset.scala
@@ -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
+)
diff --git a/src/main/scala/gitbucket/core/service/ActivityService.scala b/src/main/scala/gitbucket/core/service/ActivityService.scala
index f75a15c..2909761 100644
--- a/src/main/scala/gitbucket/core/service/ActivityService.scala
+++ b/src/main/scala/gitbucket/core/service/ActivityService.scala
@@ -190,6 +190,13 @@
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
}
diff --git a/src/main/scala/gitbucket/core/service/ReleaseService.scala b/src/main/scala/gitbucket/core/service/ReleaseService.scala
new file mode 100644
index 0000000..8aaa797
--- /dev/null
+++ b/src/main/scala/gitbucket/core/service/ReleaseService.scala
@@ -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
+ }
+ }
+}
diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala
index 7911b2e..20448c8 100644
--- a/src/main/scala/gitbucket/core/service/RepositoryService.scala
+++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala
@@ -3,7 +3,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
(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 @@
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}"
-
}
diff --git a/src/main/scala/gitbucket/core/util/Directory.scala b/src/main/scala/gitbucket/core/util/Directory.scala
index e4d3b4a..b30b455 100644
--- a/src/main/scala/gitbucket/core/util/Directory.scala
+++ b/src/main/scala/gitbucket/core/util/Directory.scala
@@ -55,6 +55,12 @@
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.
*/
def getLfsDir(owner: String, repository: String): File =
diff --git a/src/main/twirl/gitbucket/core/helper/activities.scala.html b/src/main/twirl/gitbucket/core/helper/activities.scala.html
index 8d01d0d..2b95fbc 100644
--- a/src/main/twirl/gitbucket/core/helper/activities.scala.html
+++ b/src/main/twirl/gitbucket/core/helper/activities.scala.html
@@ -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")
diff --git a/src/main/twirl/gitbucket/core/menu.scala.html b/src/main/twirl/gitbucket/core/menu.scala.html
index 6dac32a..04d9de0 100644
--- a/src/main/twirl/gitbucket/core/menu.scala.html
+++ b/src/main/twirl/gitbucket/core/menu.scala.html
@@ -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)
diff --git a/src/main/twirl/gitbucket/core/releases/form.scala.html b/src/main/twirl/gitbucket/core/releases/form.scala.html
new file mode 100644
index 0000000..7dacfab
--- /dev/null
+++ b/src/main/twirl/gitbucket/core/releases/form.scala.html
@@ -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){
+
+ }
+}
+
diff --git a/src/main/twirl/gitbucket/core/releases/list.scala.html b/src/main/twirl/gitbucket/core/releases/list.scala.html
new file mode 100644
index 0000000..ed8f4a2
--- /dev/null
+++ b/src/main/twirl/gitbucket/core/releases/list.scala.html
@@ -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){
+
+
+ @releases.length releases |
+
+
+ @releases.map { case (tag, release) =>
+
+
+
+ @tag.name
+ @tag.id.substring(0, 7)
+ @gitbucket.core.helper.html.datetimeago(tag.time)
+
+
+
+ @release.map { case (release, assets) =>
+
+
+ @helpers.avatar(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
+
+ @helpers.markdown(
+ markdown = release.content getOrElse "No description provided.",
+ repository = repository,
+ enableWikiLink = false,
+ enableRefsLink = true,
+ enableLineBreaks = true,
+ enableTaskList = true,
+ hasWritePermission = hasWritePermission
+ )
+ }.getOrElse {
+ @if(hasWritePermission){
+
+ }
+ }
+ Downloads
+
+
+
+ |
+
+ }
+
+
+ }
+}
diff --git a/src/main/twirl/gitbucket/core/releases/release.scala.html b/src/main/twirl/gitbucket/core/releases/release.scala.html
new file mode 100644
index 0000000..369ee50
--- /dev/null
+++ b/src/main/twirl/gitbucket/core/releases/release.scala.html
@@ -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){
+
+
+ @defining(repository.tags.find(_.name == release.tag)){ tag =>
+ @tag.map { tag =>
+
@tag.name
+
@tag.id.substring(0, 7)
+
@gitbucket.core.helper.html.datetimeago(tag.time)
+ }
+ }
+
+
+
+
+ @release.name
+ @if(hasWritePermission){
+
+ }
+
+
+ @helpers.avatar(release.author, 20) @helpers.user(release.author, styleClass="username") released this @gitbucket.core.helper.html.datetimeago(release.registeredDate)
+
+ @helpers.markdown(
+ markdown = release.content getOrElse "No description provided.",
+ repository = repository,
+ enableWikiLink = false,
+ enableRefsLink = true,
+ enableLineBreaks = true,
+ enableTaskList = true,
+ hasWritePermission = hasWritePermission
+ )
+
Downloads
+
+
+
+
+ }
+}
+
diff --git a/src/main/twirl/gitbucket/core/repo/tags.scala.html b/src/main/twirl/gitbucket/core/repo/tags.scala.html
deleted file mode 100644
index c3503aa..0000000
--- a/src/main/twirl/gitbucket/core/repo/tags.scala.html
+++ /dev/null
@@ -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){
-
-
-
- Tag |
- Date |
- Commit |
- Download |
-
-
-
- @repository.tags.reverseMap { tag =>
-
- @tag.name |
- @gitbucket.core.helper.html.datetimeago(tag.time, false) |
- @tag.id.substring(0, 10) |
-
- ZIP
- TAR.GZ
- |
-
- }
-
-
- }
-}