2020-01-20 23:01:19 +03:00
// 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 repository
import (
"context"
"fmt"
2020-05-17 02:31:38 +03:00
"strings"
2020-01-20 23:01:19 +03:00
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"github.com/unknwon/com"
"xorm.io/builder"
)
// GitFsck calls 'git fsck' to check repository health.
2020-05-17 02:31:38 +03:00
func GitFsck ( ctx context . Context , timeout time . Duration , args [ ] string ) error {
2020-01-20 23:01:19 +03:00
log . Trace ( "Doing: GitFsck" )
if err := models . Iterate (
models . DefaultDBContext ( ) ,
new ( models . Repository ) ,
builder . Expr ( "id>0 AND is_fsck_enabled=?" , true ) ,
func ( idx int , bean interface { } ) error {
2020-05-17 02:31:38 +03:00
repo := bean . ( * models . Repository )
2020-01-20 23:01:19 +03:00
select {
case <- ctx . Done ( ) :
2020-05-17 02:31:38 +03:00
return models . ErrCancelledf ( "before fsck of %s" , repo . FullName ( ) )
2020-01-20 23:01:19 +03:00
default :
}
2020-05-17 02:31:38 +03:00
log . Trace ( "Running health check on repository %v" , repo )
2020-01-20 23:01:19 +03:00
repoPath := repo . RepoPath ( )
2020-05-17 02:31:38 +03:00
if err := git . Fsck ( ctx , repoPath , timeout , args ... ) ; err != nil {
log . Warn ( "Failed to health check repository (%v): %v" , repo , err )
if err = models . CreateRepositoryNotice ( "Failed to health check repository (%s): %v" , repo . FullName ( ) , err ) ; err != nil {
2020-01-20 23:01:19 +03:00
log . Error ( "CreateRepositoryNotice: %v" , err )
}
}
return nil
} ,
) ; err != nil {
2020-05-17 02:31:38 +03:00
log . Trace ( "Error: GitFsck: %v" , err )
2020-01-20 23:01:19 +03:00
return err
}
log . Trace ( "Finished: GitFsck" )
return nil
}
// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
2020-05-17 02:31:38 +03:00
func GitGcRepos ( ctx context . Context , timeout time . Duration , args ... string ) error {
2020-01-20 23:01:19 +03:00
log . Trace ( "Doing: GitGcRepos" )
2020-05-17 02:31:38 +03:00
args = append ( [ ] string { "gc" } , args ... )
2020-01-20 23:01:19 +03:00
if err := models . Iterate (
models . DefaultDBContext ( ) ,
new ( models . Repository ) ,
builder . Gt { "id" : 0 } ,
func ( idx int , bean interface { } ) error {
2020-05-17 02:31:38 +03:00
repo := bean . ( * models . Repository )
2020-01-20 23:01:19 +03:00
select {
case <- ctx . Done ( ) :
2020-05-17 02:31:38 +03:00
return models . ErrCancelledf ( "before GC of %s" , repo . FullName ( ) )
2020-01-20 23:01:19 +03:00
default :
}
2020-05-17 02:31:38 +03:00
log . Trace ( "Running git gc on %v" , repo )
command := git . NewCommandContext ( ctx , args ... ) .
SetDescription ( fmt . Sprintf ( "Repository Garbage Collection: %s" , repo . FullName ( ) ) )
var stdout string
var err error
if timeout > 0 {
var stdoutBytes [ ] byte
stdoutBytes , err = command . RunInDirTimeout (
timeout ,
repo . RepoPath ( ) )
stdout = string ( stdoutBytes )
} else {
stdout , err = command . RunInDir ( repo . RepoPath ( ) )
2020-01-20 23:01:19 +03:00
}
2020-05-17 02:31:38 +03:00
if err != nil {
2020-01-20 23:01:19 +03:00
log . Error ( "Repository garbage collection failed for %v. Stdout: %s\nError: %v" , repo , stdout , err )
2020-05-17 02:31:38 +03:00
desc := fmt . Sprintf ( "Repository garbage collection failed for %s. Stdout: %s\nError: %v" , repo . RepoPath ( ) , stdout , err )
if err = models . CreateRepositoryNotice ( desc ) ; err != nil {
log . Error ( "CreateRepositoryNotice: %v" , err )
}
return fmt . Errorf ( "Repository garbage collection failed in repo: %s: Error: %v" , repo . FullName ( ) , err )
2020-01-20 23:01:19 +03:00
}
return nil
} ,
) ; err != nil {
return err
}
log . Trace ( "Finished: GitGcRepos" )
return nil
}
2020-05-17 02:31:38 +03:00
func gatherMissingRepoRecords ( ctx context . Context ) ( [ ] * models . Repository , error ) {
2020-01-20 23:01:19 +03:00
repos := make ( [ ] * models . Repository , 0 , 10 )
if err := models . Iterate (
models . DefaultDBContext ( ) ,
new ( models . Repository ) ,
builder . Gt { "id" : 0 } ,
func ( idx int , bean interface { } ) error {
repo := bean . ( * models . Repository )
2020-05-17 02:31:38 +03:00
select {
case <- ctx . Done ( ) :
return models . ErrCancelledf ( "during gathering missing repo records before checking %s" , repo . FullName ( ) )
default :
}
2020-01-20 23:01:19 +03:00
if ! com . IsDir ( repo . RepoPath ( ) ) {
repos = append ( repos , repo )
}
return nil
} ,
) ; err != nil {
2020-05-17 02:31:38 +03:00
if strings . HasPrefix ( "Aborted gathering missing repo" , err . Error ( ) ) {
return nil , err
}
if err2 := models . CreateRepositoryNotice ( "gatherMissingRepoRecords: %v" , err ) ; err2 != nil {
log . Error ( "CreateRepositoryNotice: %v" , err2 )
2020-01-20 23:01:19 +03:00
}
2020-05-17 02:31:38 +03:00
return nil , err
2020-01-20 23:01:19 +03:00
}
return repos , nil
}
// DeleteMissingRepositories deletes all repository records that lost Git files.
2020-05-17 02:31:38 +03:00
func DeleteMissingRepositories ( ctx context . Context , doer * models . User ) error {
repos , err := gatherMissingRepoRecords ( ctx )
2020-01-20 23:01:19 +03:00
if err != nil {
2020-05-17 02:31:38 +03:00
return err
2020-01-20 23:01:19 +03:00
}
if len ( repos ) == 0 {
return nil
}
for _ , repo := range repos {
2020-05-17 02:31:38 +03:00
select {
case <- ctx . Done ( ) :
return models . ErrCancelledf ( "during DeleteMissingRepositories before %s" , repo . FullName ( ) )
default :
}
2020-01-20 23:01:19 +03:00
log . Trace ( "Deleting %d/%d..." , repo . OwnerID , repo . ID )
if err := models . DeleteRepository ( doer , repo . OwnerID , repo . ID ) ; err != nil {
2020-05-17 02:31:38 +03:00
log . Error ( "Failed to DeleteRepository %s [%d]: Error: %v" , repo . FullName ( ) , repo . ID , err )
if err2 := models . CreateRepositoryNotice ( "Failed to DeleteRepository %s [%d]: Error: %v" , repo . FullName ( ) , repo . ID , err ) ; err2 != nil {
log . Error ( "CreateRepositoryNotice: %v" , err )
2020-01-20 23:01:19 +03:00
}
}
}
return nil
}
// ReinitMissingRepositories reinitializes all repository records that lost Git files.
2020-05-17 02:31:38 +03:00
func ReinitMissingRepositories ( ctx context . Context ) error {
repos , err := gatherMissingRepoRecords ( ctx )
2020-01-20 23:01:19 +03:00
if err != nil {
2020-05-17 02:31:38 +03:00
return err
2020-01-20 23:01:19 +03:00
}
if len ( repos ) == 0 {
return nil
}
for _ , repo := range repos {
2020-05-17 02:31:38 +03:00
select {
case <- ctx . Done ( ) :
return models . ErrCancelledf ( "during ReinitMissingRepositories before %s" , repo . FullName ( ) )
default :
}
2020-01-20 23:01:19 +03:00
log . Trace ( "Initializing %d/%d..." , repo . OwnerID , repo . ID )
if err := git . InitRepository ( repo . RepoPath ( ) , true ) ; err != nil {
2020-05-17 02:31:38 +03:00
log . Error ( "Unable (re)initialize repository %d at %s. Error: %v" , repo . ID , repo . RepoPath ( ) , err )
if err2 := models . CreateRepositoryNotice ( "InitRepository [%d]: %v" , repo . ID , err ) ; err2 != nil {
log . Error ( "CreateRepositoryNotice: %v" , err2 )
2020-01-20 23:01:19 +03:00
}
}
}
return nil
}