2020-01-12 15:11:17 +03:00
// Copyright 2019 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 (
2021-09-23 18:45:36 +03:00
"context"
2020-01-12 15:11:17 +03:00
"fmt"
"os"
"path"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2020-01-12 15:11:17 +03:00
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2020-08-11 23:05:34 +03:00
"code.gitea.io/gitea/modules/util"
2020-01-28 16:57:15 +03:00
"github.com/huandu/xstrings"
2020-01-12 15:11:17 +03:00
)
2020-01-28 16:57:15 +03:00
type transformer struct {
Name string
Transform func ( string ) string
}
type expansion struct {
Name string
Value string
Transformers [ ] transformer
}
var defaultTransformers = [ ] transformer {
{ Name : "SNAKE" , Transform : xstrings . ToSnakeCase } ,
{ Name : "KEBAB" , Transform : xstrings . ToKebabCase } ,
{ Name : "CAMEL" , Transform : func ( str string ) string {
return xstrings . FirstRuneToLower ( xstrings . ToCamelCase ( str ) )
} } ,
{ Name : "PASCAL" , Transform : xstrings . ToCamelCase } ,
{ Name : "LOWER" , Transform : strings . ToLower } ,
{ Name : "UPPER" , Transform : strings . ToUpper } ,
{ Name : "TITLE" , Transform : strings . Title } ,
}
2021-12-10 04:27:50 +03:00
func generateExpansion ( src string , templateRepo , generateRepo * repo_model . Repository ) string {
2020-01-28 16:57:15 +03:00
expansions := [ ] expansion {
{ Name : "REPO_NAME" , Value : generateRepo . Name , Transformers : defaultTransformers } ,
{ Name : "TEMPLATE_NAME" , Value : templateRepo . Name , Transformers : defaultTransformers } ,
{ Name : "REPO_DESCRIPTION" , Value : generateRepo . Description , Transformers : nil } ,
{ Name : "TEMPLATE_DESCRIPTION" , Value : templateRepo . Description , Transformers : nil } ,
{ Name : "REPO_OWNER" , Value : generateRepo . OwnerName , Transformers : defaultTransformers } ,
{ Name : "TEMPLATE_OWNER" , Value : templateRepo . OwnerName , Transformers : defaultTransformers } ,
{ Name : "REPO_LINK" , Value : generateRepo . Link ( ) , Transformers : nil } ,
{ Name : "TEMPLATE_LINK" , Value : templateRepo . Link ( ) , Transformers : nil } ,
{ Name : "REPO_HTTPS_URL" , Value : generateRepo . CloneLink ( ) . HTTPS , Transformers : nil } ,
{ Name : "TEMPLATE_HTTPS_URL" , Value : templateRepo . CloneLink ( ) . HTTPS , Transformers : nil } ,
{ Name : "REPO_SSH_URL" , Value : generateRepo . CloneLink ( ) . SSH , Transformers : nil } ,
{ Name : "TEMPLATE_SSH_URL" , Value : templateRepo . CloneLink ( ) . SSH , Transformers : nil } ,
}
var expansionMap = make ( map [ string ] string )
for _ , e := range expansions {
expansionMap [ e . Name ] = e . Value
for _ , tr := range e . Transformers {
expansionMap [ fmt . Sprintf ( "%s_%s" , e . Name , tr . Name ) ] = tr . Transform ( e . Value )
}
}
2020-01-12 15:11:17 +03:00
return os . Expand ( src , func ( key string ) string {
2020-01-28 16:57:15 +03:00
if expansion , ok := expansionMap [ key ] ; ok {
return expansion
2020-01-12 15:11:17 +03:00
}
2020-01-28 16:57:15 +03:00
return key
2020-01-12 15:11:17 +03:00
} )
}
func checkGiteaTemplate ( tmpDir string ) ( * models . GiteaTemplate , error ) {
gtPath := filepath . Join ( tmpDir , ".gitea" , "template" )
if _ , err := os . Stat ( gtPath ) ; os . IsNotExist ( err ) {
return nil , nil
} else if err != nil {
return nil , err
}
2021-09-22 08:38:34 +03:00
content , err := os . ReadFile ( gtPath )
2020-01-12 15:11:17 +03:00
if err != nil {
return nil , err
}
gt := & models . GiteaTemplate {
Path : gtPath ,
Content : content ,
}
return gt , nil
}
2021-12-10 04:27:50 +03:00
func generateRepoCommit ( repo , templateRepo , generateRepo * repo_model . Repository , tmpDir string ) error {
2020-01-12 15:11:17 +03:00
commitTimeStr := time . Now ( ) . Format ( time . RFC3339 )
authorSig := repo . Owner . NewGitSig ( )
// Because this may call hooks we should pass in the environment
env := append ( os . Environ ( ) ,
"GIT_AUTHOR_NAME=" + authorSig . Name ,
"GIT_AUTHOR_EMAIL=" + authorSig . Email ,
"GIT_AUTHOR_DATE=" + commitTimeStr ,
"GIT_COMMITTER_NAME=" + authorSig . Name ,
"GIT_COMMITTER_EMAIL=" + authorSig . Email ,
"GIT_COMMITTER_DATE=" + commitTimeStr ,
)
// Clone to temporary path and do the init commit.
templateRepoPath := templateRepo . RepoPath ( )
if err := git . Clone ( templateRepoPath , tmpDir , git . CloneRepoOptions {
2020-03-26 22:14:51 +03:00
Depth : 1 ,
Branch : templateRepo . DefaultBranch ,
2020-01-12 15:11:17 +03:00
} ) ; err != nil {
return fmt . Errorf ( "git clone: %v" , err )
}
2020-08-11 23:05:34 +03:00
if err := util . RemoveAll ( path . Join ( tmpDir , ".git" ) ) ; err != nil {
2020-01-12 15:11:17 +03:00
return fmt . Errorf ( "remove git dir: %v" , err )
}
// Variable expansion
gt , err := checkGiteaTemplate ( tmpDir )
if err != nil {
return fmt . Errorf ( "checkGiteaTemplate: %v" , err )
}
2020-01-28 16:57:15 +03:00
if gt != nil {
2020-08-11 23:05:34 +03:00
if err := util . Remove ( gt . Path ) ; err != nil {
2020-01-28 16:57:15 +03:00
return fmt . Errorf ( "remove .giteatemplate: %v" , err )
}
2020-01-12 15:11:17 +03:00
2020-01-28 16:57:15 +03:00
// Avoid walking tree if there are no globs
if len ( gt . Globs ( ) ) > 0 {
tmpDirSlash := strings . TrimSuffix ( filepath . ToSlash ( tmpDir ) , "/" ) + "/"
if err := filepath . Walk ( tmpDirSlash , func ( path string , info os . FileInfo , walkErr error ) error {
if walkErr != nil {
return walkErr
}
2020-01-12 15:11:17 +03:00
2020-01-28 16:57:15 +03:00
if info . IsDir ( ) {
return nil
}
2020-01-12 15:11:17 +03:00
2020-01-28 16:57:15 +03:00
base := strings . TrimPrefix ( filepath . ToSlash ( path ) , tmpDirSlash )
for _ , g := range gt . Globs ( ) {
if g . Match ( base ) {
2021-09-22 08:38:34 +03:00
content , err := os . ReadFile ( path )
2020-01-28 16:57:15 +03:00
if err != nil {
return err
}
2021-09-22 08:38:34 +03:00
if err := os . WriteFile ( path ,
2020-01-28 16:57:15 +03:00
[ ] byte ( generateExpansion ( string ( content ) , templateRepo , generateRepo ) ) ,
0644 ) ; err != nil {
return err
}
break
2020-01-12 15:11:17 +03:00
}
}
2020-01-28 16:57:15 +03:00
return nil
} ) ; err != nil {
return err
2020-01-12 15:11:17 +03:00
}
}
}
if err := git . InitRepository ( tmpDir , false ) ; err != nil {
return err
}
repoPath := repo . RepoPath ( )
if stdout , err := git . NewCommand ( "remote" , "add" , "origin" , repoPath ) .
SetDescription ( fmt . Sprintf ( "generateRepoCommit (git remote add): %s to %s" , templateRepoPath , tmpDir ) ) .
RunInDirWithEnv ( tmpDir , env ) ; err != nil {
log . Error ( "Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v" , repo , tmpDir , stdout , err )
return fmt . Errorf ( "git remote add: %v" , err )
}
2020-03-26 22:14:51 +03:00
return initRepoCommit ( tmpDir , repo , repo . Owner , templateRepo . DefaultBranch )
2020-01-12 15:11:17 +03:00
}
2021-12-10 04:27:50 +03:00
func generateGitContent ( ctx context . Context , repo , templateRepo , generateRepo * repo_model . Repository ) ( err error ) {
2021-09-22 08:38:34 +03:00
tmpDir , err := os . MkdirTemp ( os . TempDir ( ) , "gitea-" + repo . Name )
2020-01-12 15:11:17 +03:00
if err != nil {
return fmt . Errorf ( "Failed to create temp dir for repository %s: %v" , repo . RepoPath ( ) , err )
}
defer func ( ) {
2020-08-11 23:05:34 +03:00
if err := util . RemoveAll ( tmpDir ) ; err != nil {
2020-01-12 15:11:17 +03:00
log . Error ( "RemoveAll: %v" , err )
}
} ( )
if err = generateRepoCommit ( repo , templateRepo , generateRepo , tmpDir ) ; err != nil {
return fmt . Errorf ( "generateRepoCommit: %v" , err )
}
// re-fetch repo
2021-12-10 04:27:50 +03:00
if repo , err = repo_model . GetRepositoryByIDCtx ( ctx , repo . ID ) ; err != nil {
2020-01-12 15:11:17 +03:00
return fmt . Errorf ( "getRepositoryByID: %v" , err )
}
2020-03-26 22:14:51 +03:00
repo . DefaultBranch = templateRepo . DefaultBranch
2020-12-12 00:41:59 +03:00
gitRepo , err := git . OpenRepository ( repo . RepoPath ( ) )
if err != nil {
return fmt . Errorf ( "openRepository: %v" , err )
}
defer gitRepo . Close ( )
if err = gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
return fmt . Errorf ( "setDefaultBranch: %v" , err )
}
2020-01-12 15:11:17 +03:00
if err = models . UpdateRepositoryCtx ( ctx , repo , false ) ; err != nil {
return fmt . Errorf ( "updateRepository: %v" , err )
}
return nil
}
// GenerateGitContent generates git content from a template repository
2021-12-10 04:27:50 +03:00
func GenerateGitContent ( ctx context . Context , templateRepo , generateRepo * repo_model . Repository ) error {
2020-01-12 15:11:17 +03:00
if err := generateGitContent ( ctx , generateRepo , templateRepo , generateRepo ) ; err != nil {
return err
}
2021-12-10 04:27:50 +03:00
if err := models . UpdateRepoSize ( ctx , generateRepo ) ; err != nil {
2020-01-12 15:11:17 +03:00
return fmt . Errorf ( "failed to update size for repository: %v" , err )
}
if err := models . CopyLFS ( ctx , generateRepo , templateRepo ) ; err != nil {
return fmt . Errorf ( "failed to copy LFS: %v" , err )
}
return nil
}
// GenerateRepository generates a repository from a template
2021-12-10 04:27:50 +03:00
func GenerateRepository ( ctx context . Context , doer , owner * user_model . User , templateRepo * repo_model . Repository , opts models . GenerateRepoOptions ) ( _ * repo_model . Repository , err error ) {
generateRepo := & repo_model . Repository {
2020-01-12 15:11:17 +03:00
OwnerID : owner . ID ,
Owner : owner ,
OwnerName : owner . Name ,
Name : opts . Name ,
LowerName : strings . ToLower ( opts . Name ) ,
Description : opts . Description ,
IsPrivate : opts . Private ,
IsEmpty : ! opts . GitContent || templateRepo . IsEmpty ,
IsFsckEnabled : templateRepo . IsFsckEnabled ,
TemplateID : templateRepo . ID ,
2020-09-19 19:44:55 +03:00
TrustModel : templateRepo . TrustModel ,
2020-01-12 15:11:17 +03:00
}
2020-09-25 07:09:23 +03:00
if err = models . CreateRepository ( ctx , doer , owner , generateRepo , false ) ; err != nil {
2020-01-12 15:11:17 +03:00
return nil , err
}
2020-09-25 07:09:23 +03:00
repoPath := generateRepo . RepoPath ( )
2020-11-28 05:42:08 +03:00
isExist , err := util . IsExist ( repoPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , repoPath , err )
return nil , err
}
if isExist {
2021-12-12 18:48:20 +03:00
return nil , repo_model . ErrRepoFilesAlreadyExist {
2020-09-25 07:09:23 +03:00
Uname : generateRepo . OwnerName ,
Name : generateRepo . Name ,
}
}
if err = checkInitRepository ( owner . Name , generateRepo . Name ) ; err != nil {
2020-01-12 15:11:17 +03:00
return generateRepo , err
}
2021-12-10 04:27:50 +03:00
if err = models . CheckDaemonExportOK ( ctx , generateRepo ) ; err != nil {
2021-10-13 22:47:02 +03:00
return generateRepo , fmt . Errorf ( "checkDaemonExportOK: %v" , err )
}
if stdout , err := git . NewCommandContext ( ctx , "update-server-info" ) .
SetDescription ( fmt . Sprintf ( "GenerateRepository(git update-server-info): %s" , repoPath ) ) .
RunInDir ( repoPath ) ; err != nil {
log . Error ( "GenerateRepository(git update-server-info) in %v: Stdout: %s\nError: %v" , generateRepo , stdout , err )
return generateRepo , fmt . Errorf ( "error in GenerateRepository(git update-server-info): %v" , err )
}
2020-01-12 15:11:17 +03:00
return generateRepo , nil
}