2014-04-14 09:57:25 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2015-11-20 10:38:41 +03:00
"fmt"
2014-06-13 01:47:23 +04:00
"sort"
2014-04-14 09:57:25 +04:00
"strings"
"time"
2016-11-10 19:24:48 +03:00
"code.gitea.io/git"
"code.gitea.io/gitea/modules/process"
2016-12-31 19:51:22 +03:00
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/sdk/gitea"
2017-06-28 17:47:00 +03:00
"github.com/go-xorm/builder"
2017-01-17 08:58:58 +03:00
"github.com/go-xorm/xorm"
2014-04-14 09:57:25 +04:00
)
// Release represents a release of repository.
type Release struct {
2017-01-06 04:51:15 +03:00
ID int64 ` xorm:"pk autoincr" `
2017-01-06 18:14:33 +03:00
RepoID int64 ` xorm:"INDEX UNIQUE(n)" `
2016-12-31 19:51:22 +03:00
Repo * Repository ` xorm:"-" `
2017-01-06 18:14:33 +03:00
PublisherID int64 ` xorm:"INDEX" `
Publisher * User ` xorm:"-" `
TagName string ` xorm:"INDEX UNIQUE(n)" `
2014-04-14 09:57:25 +04:00
LowerTagName string
2014-06-12 17:10:39 +04:00
Target string
2014-06-13 01:47:23 +04:00
Title string
2014-06-12 17:10:39 +04:00
Sha1 string ` xorm:"VARCHAR(40)" `
2015-12-10 04:46:05 +03:00
NumCommits int64
NumCommitsBehind int64 ` xorm:"-" `
2014-04-14 09:57:25 +04:00
Note string ` xorm:"TEXT" `
2014-06-13 01:47:23 +04:00
IsDraft bool ` xorm:"NOT NULL DEFAULT false" `
2014-04-14 09:57:25 +04:00
IsPrerelease bool
2016-03-10 03:53:30 +03:00
2017-01-15 17:57:00 +03:00
Attachments [ ] * Attachment ` xorm:"-" `
2016-03-10 03:53:30 +03:00
Created time . Time ` xorm:"-" `
2017-01-06 18:14:33 +03:00
CreatedUnix int64 ` xorm:"INDEX" `
2016-03-10 03:53:30 +03:00
}
2016-11-25 11:11:12 +03:00
// BeforeInsert is invoked from XORM before inserting an object of this type.
2016-03-10 03:53:30 +03:00
func ( r * Release ) BeforeInsert ( ) {
2016-08-06 20:02:15 +03:00
if r . CreatedUnix == 0 {
r . CreatedUnix = time . Now ( ) . Unix ( )
}
2014-04-14 09:57:25 +04:00
}
2016-11-25 11:11:12 +03:00
// AfterSet is invoked from XORM after setting the value of a field of this object.
2015-08-24 16:01:23 +03:00
func ( r * Release ) AfterSet ( colName string , _ xorm . Cell ) {
switch colName {
2016-03-10 03:53:30 +03:00
case "created_unix" :
r . Created = time . Unix ( r . CreatedUnix , 0 ) . Local ( )
2015-08-24 16:01:23 +03:00
}
}
2016-12-31 19:51:22 +03:00
func ( r * Release ) loadAttributes ( e Engine ) error {
var err error
if r . Repo == nil {
r . Repo , err = GetRepositoryByID ( r . RepoID )
if err != nil {
return err
}
}
if r . Publisher == nil {
r . Publisher , err = GetUserByID ( r . PublisherID )
if err != nil {
return err
}
}
return nil
}
2017-03-15 03:52:01 +03:00
// LoadAttributes load repo and publisher attributes for a release
2016-12-31 19:51:22 +03:00
func ( r * Release ) LoadAttributes ( ) error {
return r . loadAttributes ( x )
}
// APIURL the api url for a release. release must have attributes loaded
func ( r * Release ) APIURL ( ) string {
return fmt . Sprintf ( "%sapi/v1/%s/releases/%d" ,
setting . AppURL , r . Repo . FullName ( ) , r . ID )
}
// ZipURL the zip url for a release. release must have attributes loaded
func ( r * Release ) ZipURL ( ) string {
return fmt . Sprintf ( "%s/archive/%s.zip" , r . Repo . HTMLURL ( ) , r . TagName )
}
// TarURL the tar.gz url for a release. release must have attributes loaded
func ( r * Release ) TarURL ( ) string {
return fmt . Sprintf ( "%s/archive/%s.tar.gz" , r . Repo . HTMLURL ( ) , r . TagName )
}
// APIFormat convert a Release to api.Release
func ( r * Release ) APIFormat ( ) * api . Release {
return & api . Release {
ID : r . ID ,
TagName : r . TagName ,
Target : r . Target ,
Note : r . Note ,
URL : r . APIURL ( ) ,
TarURL : r . TarURL ( ) ,
ZipURL : r . ZipURL ( ) ,
IsDraft : r . IsDraft ,
IsPrerelease : r . IsPrerelease ,
CreatedAt : r . Created ,
PublishedAt : r . Created ,
Publisher : r . Publisher . APIFormat ( ) ,
}
}
2014-04-14 09:57:25 +04:00
// IsReleaseExist returns true if release with given tag name already exists.
2015-11-16 07:52:46 +03:00
func IsReleaseExist ( repoID int64 , tagName string ) ( bool , error ) {
2014-04-14 09:57:25 +04:00
if len ( tagName ) == 0 {
return false , nil
}
2015-11-16 07:52:46 +03:00
return x . Get ( & Release { RepoID : repoID , LowerTagName : strings . ToLower ( tagName ) } )
2014-04-14 09:57:25 +04:00
}
2014-06-13 01:47:23 +04:00
func createTag ( gitRepo * git . Repository , rel * Release ) error {
// Only actual create when publish.
if ! rel . IsDraft {
if ! gitRepo . IsTagExist ( rel . TagName ) {
2015-12-10 04:46:05 +03:00
commit , err := gitRepo . GetBranchCommit ( rel . Target )
2014-06-13 01:47:23 +04:00
if err != nil {
2015-12-10 04:46:05 +03:00
return fmt . Errorf ( "GetBranchCommit: %v" , err )
2014-06-13 01:47:23 +04:00
}
2016-07-23 10:59:19 +03:00
// Trim '--' prefix to prevent command line argument vulnerability.
2016-05-06 22:40:41 +03:00
rel . TagName = strings . TrimPrefix ( rel . TagName , "--" )
2015-11-04 06:49:06 +03:00
if err = gitRepo . CreateTag ( rel . TagName , commit . ID . String ( ) ) ; err != nil {
2016-07-23 10:59:19 +03:00
if strings . Contains ( err . Error ( ) , "is not a valid tag name" ) {
return ErrInvalidTagName { rel . TagName }
}
2014-06-13 01:47:23 +04:00
return err
}
} else {
2015-12-10 04:46:05 +03:00
commit , err := gitRepo . GetTagCommit ( rel . TagName )
2014-06-13 01:47:23 +04:00
if err != nil {
2015-12-10 04:46:05 +03:00
return fmt . Errorf ( "GetTagCommit: %v" , err )
2014-06-13 01:47:23 +04:00
}
2016-07-28 05:45:35 +03:00
rel . Sha1 = commit . ID . String ( )
2014-06-13 01:47:23 +04:00
rel . NumCommits , err = commit . CommitsCount ( )
if err != nil {
2015-12-10 04:46:05 +03:00
return fmt . Errorf ( "CommitsCount: %v" , err )
2014-06-13 01:47:23 +04:00
}
}
}
return nil
}
2017-01-15 17:57:00 +03:00
func addReleaseAttachments ( releaseID int64 , attachmentUUIDs [ ] string ) ( err error ) {
// Check attachments
2017-01-17 08:58:58 +03:00
var attachments = make ( [ ] * Attachment , 0 )
2017-01-15 17:57:00 +03:00
for _ , uuid := range attachmentUUIDs {
attach , err := getAttachmentByUUID ( x , uuid )
if err != nil {
if IsErrAttachmentNotExist ( err ) {
continue
}
return fmt . Errorf ( "getAttachmentByUUID [%s]: %v" , uuid , err )
}
attachments = append ( attachments , attach )
}
for i := range attachments {
attachments [ i ] . ReleaseID = releaseID
// No assign value could be 0, so ignore AllCols().
if _ , err = x . Id ( attachments [ i ] . ID ) . Update ( attachments [ i ] ) ; err != nil {
return fmt . Errorf ( "update attachment [%d]: %v" , attachments [ i ] . ID , err )
}
}
return
}
2014-04-14 09:57:25 +04:00
// CreateRelease creates a new release of repository.
2017-01-15 17:57:00 +03:00
func CreateRelease ( gitRepo * git . Repository , rel * Release , attachmentUUIDs [ ] string ) error {
2015-11-16 07:52:46 +03:00
isExist , err := IsReleaseExist ( rel . RepoID , rel . TagName )
2014-04-14 09:57:25 +04:00
if err != nil {
return err
} else if isExist {
2015-11-16 07:52:46 +03:00
return ErrReleaseAlreadyExist { rel . TagName }
2014-04-14 09:57:25 +04:00
}
2014-06-13 01:47:23 +04:00
if err = createTag ( gitRepo , rel ) ; err != nil {
return err
}
rel . LowerTagName = strings . ToLower ( rel . TagName )
2017-01-15 17:57:00 +03:00
2014-06-21 08:51:41 +04:00
_ , err = x . InsertOne ( rel )
2017-01-15 17:57:00 +03:00
if err != nil {
return err
}
err = addReleaseAttachments ( rel . ID , attachmentUUIDs )
2014-06-13 01:47:23 +04:00
return err
}
2014-06-12 17:10:39 +04:00
2014-06-13 01:47:23 +04:00
// GetRelease returns release by given ID.
2015-11-16 07:52:46 +03:00
func GetRelease ( repoID int64 , tagName string ) ( * Release , error ) {
isExist , err := IsReleaseExist ( repoID , tagName )
2014-06-13 01:47:23 +04:00
if err != nil {
return nil , err
} else if ! isExist {
2015-11-20 10:38:41 +03:00
return nil , ErrReleaseNotExist { 0 , tagName }
2014-06-13 01:47:23 +04:00
}
2014-04-14 09:57:25 +04:00
2015-11-16 07:52:46 +03:00
rel := & Release { RepoID : repoID , LowerTagName : strings . ToLower ( tagName ) }
2014-06-21 08:51:41 +04:00
_ , err = x . Get ( rel )
2014-06-13 01:47:23 +04:00
return rel , err
}
2015-11-20 10:38:41 +03:00
// GetReleaseByID returns release with given ID.
func GetReleaseByID ( id int64 ) ( * Release , error ) {
rel := new ( Release )
2016-11-10 18:16:32 +03:00
has , err := x .
Id ( id ) .
Get ( rel )
2015-11-20 10:38:41 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrReleaseNotExist { id , "" }
}
return rel , nil
}
2017-06-29 18:11:38 +03:00
// FindReleasesOptions describes the conditions to Find releases
type FindReleasesOptions struct {
IncludeDrafts bool
TagNames [ ] string
2014-06-13 01:47:23 +04:00
}
2017-06-29 18:11:38 +03:00
func ( opts * FindReleasesOptions ) toConds ( repoID int64 ) builder . Cond {
2017-06-28 17:47:00 +03:00
var cond = builder . NewCond ( )
cond = cond . And ( builder . Eq { "repo_id" : repoID } )
2017-06-29 18:11:38 +03:00
if ! opts . IncludeDrafts {
cond = cond . And ( builder . Eq { "is_draft" : false } )
2017-06-28 17:47:00 +03:00
}
2017-06-29 18:11:38 +03:00
if len ( opts . TagNames ) > 0 {
cond = cond . And ( builder . In ( "tag_name" , opts . TagNames ) )
}
return cond
2017-06-28 17:47:00 +03:00
}
2017-06-29 18:11:38 +03:00
// GetReleasesByRepoID returns a list of releases of repository.
func GetReleasesByRepoID ( repoID int64 , opts FindReleasesOptions , page , pageSize int ) ( rels [ ] * Release , err error ) {
if page <= 0 {
page = 1
}
2017-01-06 04:51:15 +03:00
err = x .
2017-06-29 18:11:38 +03:00
Desc ( "created_unix" , "id" ) .
Limit ( pageSize , ( page - 1 ) * pageSize ) .
Where ( opts . toConds ( repoID ) ) .
Find ( & rels )
2017-01-06 04:51:15 +03:00
return rels , err
}
2017-06-29 18:11:38 +03:00
// GetReleaseCountByRepoID returns the count of releases of repository
func GetReleaseCountByRepoID ( repoID int64 , opts FindReleasesOptions ) ( int64 , error ) {
return x . Where ( opts . toConds ( repoID ) ) . Count ( & Release { } )
}
2017-01-15 17:57:00 +03:00
type releaseMetaSearch struct {
2017-01-17 08:58:58 +03:00
ID [ ] int64
Rel [ ] * Release
2017-01-15 17:57:00 +03:00
}
2017-01-17 08:58:58 +03:00
2017-01-15 17:57:00 +03:00
func ( s releaseMetaSearch ) Len ( ) int {
return len ( s . ID )
}
func ( s releaseMetaSearch ) Swap ( i , j int ) {
s . ID [ i ] , s . ID [ j ] = s . ID [ j ] , s . ID [ i ]
s . Rel [ i ] , s . Rel [ j ] = s . Rel [ j ] , s . Rel [ i ]
}
func ( s releaseMetaSearch ) Less ( i , j int ) bool {
return s . ID [ i ] < s . ID [ j ]
}
// GetReleaseAttachments retrieves the attachments for releases
2017-01-17 08:58:58 +03:00
func GetReleaseAttachments ( rels ... * Release ) ( err error ) {
2017-01-15 17:57:00 +03:00
if len ( rels ) == 0 {
return
}
2017-01-17 08:58:58 +03:00
// To keep this efficient as possible sort all releases by id,
2017-01-15 17:57:00 +03:00
// select attachments by release id,
// then merge join them
// Sort
var sortedRels = releaseMetaSearch { ID : make ( [ ] int64 , len ( rels ) ) , Rel : make ( [ ] * Release , len ( rels ) ) }
2017-01-17 08:58:58 +03:00
var attachments [ ] * Attachment
2017-01-15 17:57:00 +03:00
for index , element := range rels {
element . Attachments = [ ] * Attachment { }
sortedRels . ID [ index ] = element . ID
sortedRels . Rel [ index ] = element
}
sort . Sort ( sortedRels )
// Select attachments
err = x .
Asc ( "release_id" ) .
In ( "release_id" , sortedRels . ID ) .
Find ( & attachments , Attachment { } )
if err != nil {
return err
}
// merge join
var currentIndex = 0
for _ , attachment := range attachments {
for sortedRels . ID [ currentIndex ] < attachment . ReleaseID {
currentIndex ++
}
sortedRels . Rel [ currentIndex ] . Attachments = append ( sortedRels . Rel [ currentIndex ] . Attachments , attachment )
}
return
}
2016-11-25 11:11:12 +03:00
type releaseSorter struct {
2014-06-13 01:47:23 +04:00
rels [ ] * Release
}
2016-11-25 11:11:12 +03:00
func ( rs * releaseSorter ) Len ( ) int {
2014-06-13 01:47:23 +04:00
return len ( rs . rels )
}
2016-11-25 11:11:12 +03:00
func ( rs * releaseSorter ) Less ( i , j int ) bool {
2014-06-13 01:47:23 +04:00
diffNum := rs . rels [ i ] . NumCommits - rs . rels [ j ] . NumCommits
if diffNum != 0 {
return diffNum > 0
2014-04-14 09:57:25 +04:00
}
2014-06-13 01:47:23 +04:00
return rs . rels [ i ] . Created . After ( rs . rels [ j ] . Created )
}
2014-04-14 09:57:25 +04:00
2016-11-25 11:11:12 +03:00
func ( rs * releaseSorter ) Swap ( i , j int ) {
2014-06-13 01:47:23 +04:00
rs . rels [ i ] , rs . rels [ j ] = rs . rels [ j ] , rs . rels [ i ]
}
// SortReleases sorts releases by number of commits and created time.
func SortReleases ( rels [ ] * Release ) {
2016-11-25 11:11:12 +03:00
sorter := & releaseSorter { rels : rels }
2014-06-13 01:47:23 +04:00
sort . Sort ( sorter )
}
// UpdateRelease updates information of a release.
2017-01-15 17:57:00 +03:00
func UpdateRelease ( gitRepo * git . Repository , rel * Release , attachmentUUIDs [ ] string ) ( err error ) {
2014-06-13 01:47:23 +04:00
if err = createTag ( gitRepo , rel ) ; err != nil {
return err
}
2015-11-16 07:52:46 +03:00
_ , err = x . Id ( rel . ID ) . AllCols ( ) . Update ( rel )
2017-01-15 17:57:00 +03:00
if err != nil {
return err
}
err = addReleaseAttachments ( rel . ID , attachmentUUIDs )
2014-04-14 09:57:25 +04:00
return err
}
2015-11-20 10:38:41 +03:00
// DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
2016-12-31 14:51:12 +03:00
func DeleteReleaseByID ( id int64 , u * User , delTag bool ) error {
2015-11-20 10:38:41 +03:00
rel , err := GetReleaseByID ( id )
if err != nil {
return fmt . Errorf ( "GetReleaseByID: %v" , err )
}
repo , err := GetRepositoryByID ( rel . RepoID )
if err != nil {
return fmt . Errorf ( "GetRepositoryByID: %v" , err )
}
2017-03-15 03:51:46 +03:00
has , err := HasAccess ( u . ID , repo , AccessModeWrite )
2016-12-16 14:42:39 +03:00
if err != nil {
return fmt . Errorf ( "HasAccess: %v" , err )
} else if ! has {
return fmt . Errorf ( "DeleteReleaseByID: permission denied" )
}
2016-12-31 14:51:12 +03:00
if delTag {
2017-01-17 08:58:58 +03:00
_ , stderr , err := process . GetManager ( ) . ExecDir ( - 1 , repo . RepoPath ( ) ,
2016-12-31 14:51:12 +03:00
fmt . Sprintf ( "DeleteReleaseByID (git tag -d): %d" , rel . ID ) ,
"git" , "tag" , "-d" , rel . TagName )
if err != nil && ! strings . Contains ( stderr , "not found" ) {
return fmt . Errorf ( "git tag -d: %v - %s" , err , stderr )
}
2015-11-20 10:38:41 +03:00
}
if _ , err = x . Id ( rel . ID ) . Delete ( new ( Release ) ) ; err != nil {
return fmt . Errorf ( "Delete: %v" , err )
}
return nil
}