// Copyright 2020 The Gitea 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 migrations
import (
"fmt"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
)
// Copy paste from models/repo.go because we cannot import models package
func repoPath ( userName , repoName string ) string {
return filepath . Join ( userPath ( userName ) , strings . ToLower ( repoName ) + ".git" )
}
func userPath ( userName string ) string {
return filepath . Join ( setting . RepoRootPath , strings . ToLower ( userName ) )
}
func fixPublisherIDforTagReleases ( x * xorm . Engine ) error {
type Release struct {
ID int64
RepoID int64
Sha1 string
TagName string
PublisherID int64
}
type Repository struct {
ID int64
OwnerID int64
OwnerName string
Name string
}
type User struct {
ID int64
Name string
Email string
}
const batchSize = 100
sess := x . NewSession ( )
defer sess . Close ( )
var (
repo * Repository
gitRepo * git . Repository
user * User
)
defer func ( ) {
if gitRepo != nil {
gitRepo . Close ( )
}
} ( )
for start := 0 ; ; start += batchSize {
releases := make ( [ ] * Release , 0 , batchSize )
if err := sess . Begin ( ) ; err != nil {
return err
}
if err := sess . Limit ( batchSize , start ) .
Where ( "publisher_id = 0 OR publisher_id is null" ) .
Asc ( "repo_id" , "id" ) . Where ( "is_tag=?" , true ) .
Find ( & releases ) ; err != nil {
return err
}
if len ( releases ) == 0 {
break
}
for _ , release := range releases {
if repo == nil || repo . ID != release . RepoID {
if gitRepo != nil {
gitRepo . Close ( )
gitRepo = nil
}
repo = new ( Repository )
has , err := sess . ID ( release . RepoID ) . Get ( repo )
if err != nil {
log . Error ( "Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v" , release . RepoID , release . ID , release . TagName , err )
return err
} else if ! has {
log . Warn ( "Release[%d] is orphaned and refers to non-existing repository %d" , release . ID , release . RepoID )
log . Warn ( "This release should be deleted" )
continue
}
if repo . OwnerName == "" {
// v120.go migration may not have been run correctly - we'll just replicate it here
// because this appears to be a common-ish problem.
if _ , err := sess . Exec ( "UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)" ) ; err != nil {
log . Error ( "Error whilst updating repository[%d] owner name" , repo . ID )
return err
}
if _ , err := sess . ID ( release . RepoID ) . Get ( repo ) ; err != nil {
log . Error ( "Error whilst loading repository[%d] for release[%d] with tag name %s. Error: %v" , release . RepoID , release . ID , release . TagName , err )
return err
}
}
gitRepo , err = git . OpenRepository ( git . DefaultContext , repoPath ( repo . OwnerName , repo . Name ) )
if err != nil {
log . Error ( "Error whilst opening git repo for [%d]%s/%s. Error: %v" , repo . ID , repo . OwnerName , repo . Name , err )
return err
}
}
commit , err := gitRepo . GetTagCommit ( release . TagName )
if err != nil {
if git . IsErrNotExist ( err ) {
log . Warn ( "Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID." , err . ( git . ErrNotExist ) . ID , release . TagName , repo . ID , repo . OwnerName , repo . Name )
continue
}
log . Error ( "Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v" , release . TagName , repo . ID , repo . OwnerName , repo . Name , err )
return fmt . Errorf ( "GetTagCommit: %v" , err )
}
if commit . Author . Email == "" {
log . Warn ( "Tag: %s in Repo[%d]%s/%s does not have a tagger." , release . TagName , repo . ID , repo . OwnerName , repo . Name )
commit , err = gitRepo . GetCommit ( commit . ID . String ( ) )
if err != nil {
if git . IsErrNotExist ( err ) {
log . Warn ( "Unable to find commit %s for Tag: %s in [%d]%s/%s. Cannot update publisher ID." , err . ( git . ErrNotExist ) . ID , release . TagName , repo . ID , repo . OwnerName , repo . Name )
continue
}
log . Error ( "Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v" , release . TagName , repo . ID , repo . OwnerName , repo . Name , err )
return fmt . Errorf ( "GetCommit: %v" , err )
}
}
if commit . Author . Email == "" {
log . Warn ( "Tag: %s in Repo[%d]%s/%s does not have a Tagger and its underlying commit does not have an Author either!" , release . TagName , repo . ID , repo . OwnerName , repo . Name )
continue
}
if user == nil || ! strings . EqualFold ( user . Email , commit . Author . Email ) {
user = new ( User )
_ , err = sess . Where ( "email=?" , commit . Author . Email ) . Get ( user )
if err != nil {
log . Error ( "Error whilst getting commit author by email: %s for Tag: %s in [%d]%s/%s. Error: %v" , commit . Author . Email , release . TagName , repo . ID , repo . OwnerName , repo . Name , err )
return err
}
user . Email = commit . Author . Email
}
if user . ID <= 0 {
continue
}
release . PublisherID = user . ID
if _ , err := sess . ID ( release . ID ) . Cols ( "publisher_id" ) . Update ( release ) ; err != nil {
log . Error ( "Error whilst updating publisher[%d] for release[%d] with tag name %s. Error: %v" , release . PublisherID , release . ID , release . TagName , err )
return err
}
}
if gitRepo != nil {
gitRepo . Close ( )
}
if err := sess . Commit ( ) ; err != nil {
return err
}
}
return nil
}