mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Move createrepository from module to service layer (#26927)
Repository creation depends on many models, so moving it to service layer is better.
This commit is contained in:
		| @@ -22,7 +22,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/models/webhook" | 	"code.gitea.io/gitea/models/webhook" | ||||||
| 	"code.gitea.io/gitea/modules/git" |  | ||||||
| 	issue_indexer "code.gitea.io/gitea/modules/indexer/issues" | 	issue_indexer "code.gitea.io/gitea/modules/indexer/issues" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| @@ -156,142 +155,6 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // CreateRepoOptions contains the create repository options |  | ||||||
| type CreateRepoOptions struct { |  | ||||||
| 	Name           string |  | ||||||
| 	Description    string |  | ||||||
| 	OriginalURL    string |  | ||||||
| 	GitServiceType api.GitServiceType |  | ||||||
| 	Gitignores     string |  | ||||||
| 	IssueLabels    string |  | ||||||
| 	License        string |  | ||||||
| 	Readme         string |  | ||||||
| 	DefaultBranch  string |  | ||||||
| 	IsPrivate      bool |  | ||||||
| 	IsMirror       bool |  | ||||||
| 	IsTemplate     bool |  | ||||||
| 	AutoInit       bool |  | ||||||
| 	Status         repo_model.RepositoryStatus |  | ||||||
| 	TrustModel     repo_model.TrustModelType |  | ||||||
| 	MirrorInterval string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CreateRepository creates a repository for the user/organization. |  | ||||||
| func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { |  | ||||||
| 	if !doer.IsAdmin && !u.CanCreateRepo() { |  | ||||||
| 		return nil, repo_model.ErrReachLimitOfRepo{ |  | ||||||
| 			Limit: u.MaxRepoCreation, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(opts.DefaultBranch) == 0 { |  | ||||||
| 		opts.DefaultBranch = setting.Repository.DefaultBranch |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Check if label template exist |  | ||||||
| 	if len(opts.IssueLabels) > 0 { |  | ||||||
| 		if _, err := LoadTemplateLabelsByDisplayName(opts.IssueLabels); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	repo := &repo_model.Repository{ |  | ||||||
| 		OwnerID:                         u.ID, |  | ||||||
| 		Owner:                           u, |  | ||||||
| 		OwnerName:                       u.Name, |  | ||||||
| 		Name:                            opts.Name, |  | ||||||
| 		LowerName:                       strings.ToLower(opts.Name), |  | ||||||
| 		Description:                     opts.Description, |  | ||||||
| 		OriginalURL:                     opts.OriginalURL, |  | ||||||
| 		OriginalServiceType:             opts.GitServiceType, |  | ||||||
| 		IsPrivate:                       opts.IsPrivate, |  | ||||||
| 		IsFsckEnabled:                   !opts.IsMirror, |  | ||||||
| 		IsTemplate:                      opts.IsTemplate, |  | ||||||
| 		CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, |  | ||||||
| 		Status:                          opts.Status, |  | ||||||
| 		IsEmpty:                         !opts.AutoInit, |  | ||||||
| 		TrustModel:                      opts.TrustModel, |  | ||||||
| 		IsMirror:                        opts.IsMirror, |  | ||||||
| 		DefaultBranch:                   opts.DefaultBranch, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var rollbackRepo *repo_model.Repository |  | ||||||
|  |  | ||||||
| 	if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { |  | ||||||
| 		if err := CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// No need for init mirror. |  | ||||||
| 		if opts.IsMirror { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		repoPath := repo_model.RepoPath(u.Name, repo.Name) |  | ||||||
| 		isExist, err := util.IsExist(repoPath) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error("Unable to check if %s exists. Error: %v", repoPath, err) |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		if isExist { |  | ||||||
| 			// repo already exists - We have two or three options. |  | ||||||
| 			// 1. We fail stating that the directory exists |  | ||||||
| 			// 2. We create the db repository to go with this data and adopt the git repo |  | ||||||
| 			// 3. We delete it and start afresh |  | ||||||
| 			// |  | ||||||
| 			// Previously Gitea would just delete and start afresh - this was naughty. |  | ||||||
| 			// So we will now fail and delegate to other functionality to adopt or delete |  | ||||||
| 			log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath) |  | ||||||
| 			return repo_model.ErrRepoFilesAlreadyExist{ |  | ||||||
| 				Uname: u.Name, |  | ||||||
| 				Name:  repo.Name, |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if err = initRepository(ctx, repoPath, doer, repo, opts); err != nil { |  | ||||||
| 			if err2 := util.RemoveAll(repoPath); err2 != nil { |  | ||||||
| 				log.Error("initRepository: %v", err) |  | ||||||
| 				return fmt.Errorf( |  | ||||||
| 					"delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) |  | ||||||
| 			} |  | ||||||
| 			return fmt.Errorf("initRepository: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Initialize Issue Labels if selected |  | ||||||
| 		if len(opts.IssueLabels) > 0 { |  | ||||||
| 			if err = InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { |  | ||||||
| 				rollbackRepo = repo |  | ||||||
| 				rollbackRepo.OwnerID = u.ID |  | ||||||
| 				return fmt.Errorf("InitializeLabels: %w", err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if err := CheckDaemonExportOK(ctx, repo); err != nil { |  | ||||||
| 			return fmt.Errorf("checkDaemonExportOK: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if stdout, _, err := git.NewCommand(ctx, "update-server-info"). |  | ||||||
| 			SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). |  | ||||||
| 			RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { |  | ||||||
| 			log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) |  | ||||||
| 			rollbackRepo = repo |  | ||||||
| 			rollbackRepo.OwnerID = u.ID |  | ||||||
| 			return fmt.Errorf("CreateRepository(git update-server-info): %w", err) |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	}); err != nil { |  | ||||||
| 		if rollbackRepo != nil { |  | ||||||
| 			if errDelete := models.DeleteRepository(doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil { |  | ||||||
| 				log.Error("Rollback deleteRepository: %v", errDelete) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return repo, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular | const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular | ||||||
|  |  | ||||||
| // getDirectorySize returns the disk consumption for a given path | // getDirectorySize returns the disk consumption for a given path | ||||||
|   | |||||||
| @@ -4,151 +4,16 @@ | |||||||
| package repository | package repository | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models" |  | ||||||
| 	activities_model "code.gitea.io/gitea/models/activities" | 	activities_model "code.gitea.io/gitea/models/activities" | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	"code.gitea.io/gitea/models/organization" |  | ||||||
| 	"code.gitea.io/gitea/models/perm" |  | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	"code.gitea.io/gitea/models/unittest" | 	"code.gitea.io/gitea/models/unittest" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" |  | ||||||
| 	"code.gitea.io/gitea/modules/structs" |  | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestIncludesAllRepositoriesTeams(t *testing.T) { |  | ||||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) |  | ||||||
|  |  | ||||||
| 	testTeamRepositories := func(teamID int64, repoIds []int64) { |  | ||||||
| 		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) |  | ||||||
| 		assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name) |  | ||||||
| 		assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) |  | ||||||
| 		assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name) |  | ||||||
| 		for i, rid := range repoIds { |  | ||||||
| 			if rid > 0 { |  | ||||||
| 				assert.True(t, models.HasRepository(team, rid), "%s: HasRepository(%d) %d", rid, i) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Get an admin user. |  | ||||||
| 	user, err := user_model.GetUserByID(db.DefaultContext, 1) |  | ||||||
| 	assert.NoError(t, err, "GetUserByID") |  | ||||||
|  |  | ||||||
| 	// Create org. |  | ||||||
| 	org := &organization.Organization{ |  | ||||||
| 		Name:       "All_repo", |  | ||||||
| 		IsActive:   true, |  | ||||||
| 		Type:       user_model.UserTypeOrganization, |  | ||||||
| 		Visibility: structs.VisibleTypePublic, |  | ||||||
| 	} |  | ||||||
| 	assert.NoError(t, organization.CreateOrganization(org, user), "CreateOrganization") |  | ||||||
|  |  | ||||||
| 	// Check Owner team. |  | ||||||
| 	ownerTeam, err := org.GetOwnerTeam(db.DefaultContext) |  | ||||||
| 	assert.NoError(t, err, "GetOwnerTeam") |  | ||||||
| 	assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories") |  | ||||||
|  |  | ||||||
| 	// Create repos. |  | ||||||
| 	repoIds := make([]int64, 0) |  | ||||||
| 	for i := 0; i < 3; i++ { |  | ||||||
| 		r, err := CreateRepository(user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}) |  | ||||||
| 		assert.NoError(t, err, "CreateRepository %d", i) |  | ||||||
| 		if r != nil { |  | ||||||
| 			repoIds = append(repoIds, r.ID) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Get fresh copy of Owner team after creating repos. |  | ||||||
| 	ownerTeam, err = org.GetOwnerTeam(db.DefaultContext) |  | ||||||
| 	assert.NoError(t, err, "GetOwnerTeam") |  | ||||||
|  |  | ||||||
| 	// Create teams and check repositories. |  | ||||||
| 	teams := []*organization.Team{ |  | ||||||
| 		ownerTeam, |  | ||||||
| 		{ |  | ||||||
| 			OrgID:                   org.ID, |  | ||||||
| 			Name:                    "team one", |  | ||||||
| 			AccessMode:              perm.AccessModeRead, |  | ||||||
| 			IncludesAllRepositories: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			OrgID:                   org.ID, |  | ||||||
| 			Name:                    "team 2", |  | ||||||
| 			AccessMode:              perm.AccessModeRead, |  | ||||||
| 			IncludesAllRepositories: false, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			OrgID:                   org.ID, |  | ||||||
| 			Name:                    "team three", |  | ||||||
| 			AccessMode:              perm.AccessModeWrite, |  | ||||||
| 			IncludesAllRepositories: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			OrgID:                   org.ID, |  | ||||||
| 			Name:                    "team 4", |  | ||||||
| 			AccessMode:              perm.AccessModeWrite, |  | ||||||
| 			IncludesAllRepositories: false, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	teamRepos := [][]int64{ |  | ||||||
| 		repoIds, |  | ||||||
| 		repoIds, |  | ||||||
| 		{}, |  | ||||||
| 		repoIds, |  | ||||||
| 		{}, |  | ||||||
| 	} |  | ||||||
| 	for i, team := range teams { |  | ||||||
| 		if i > 0 { // first team is Owner. |  | ||||||
| 			assert.NoError(t, models.NewTeam(team), "%s: NewTeam", team.Name) |  | ||||||
| 		} |  | ||||||
| 		testTeamRepositories(team.ID, teamRepos[i]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Update teams and check repositories. |  | ||||||
| 	teams[3].IncludesAllRepositories = false |  | ||||||
| 	teams[4].IncludesAllRepositories = true |  | ||||||
| 	teamRepos[4] = repoIds |  | ||||||
| 	for i, team := range teams { |  | ||||||
| 		assert.NoError(t, models.UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name) |  | ||||||
| 		testTeamRepositories(team.ID, teamRepos[i]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Create repo and check teams repositories. |  | ||||||
| 	r, err := CreateRepository(user, org.AsUser(), CreateRepoOptions{Name: "repo-last"}) |  | ||||||
| 	assert.NoError(t, err, "CreateRepository last") |  | ||||||
| 	if r != nil { |  | ||||||
| 		repoIds = append(repoIds, r.ID) |  | ||||||
| 	} |  | ||||||
| 	teamRepos[0] = repoIds |  | ||||||
| 	teamRepos[1] = repoIds |  | ||||||
| 	teamRepos[4] = repoIds |  | ||||||
| 	for i, team := range teams { |  | ||||||
| 		testTeamRepositories(team.ID, teamRepos[i]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Remove repo and check teams repositories. |  | ||||||
| 	assert.NoError(t, models.DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository") |  | ||||||
| 	teamRepos[0] = repoIds[1:] |  | ||||||
| 	teamRepos[1] = repoIds[1:] |  | ||||||
| 	teamRepos[3] = repoIds[1:3] |  | ||||||
| 	teamRepos[4] = repoIds[1:] |  | ||||||
| 	for i, team := range teams { |  | ||||||
| 		testTeamRepositories(team.ID, teamRepos[i]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Wipe created items. |  | ||||||
| 	for i, rid := range repoIds { |  | ||||||
| 		if i > 0 { // first repo already deleted. |  | ||||||
| 			assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestUpdateRepositoryVisibilityChanged(t *testing.T) { | func TestUpdateRepositoryVisibilityChanged(t *testing.T) { | ||||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -241,7 +241,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r | |||||||
| 		defaultBranch = templateRepo.DefaultBranch | 		defaultBranch = templateRepo.DefaultBranch | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch) | 	return InitRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch) | ||||||
| } | } | ||||||
|  |  | ||||||
| func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { | func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { | ||||||
| @@ -356,7 +356,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err = checkInitRepository(ctx, owner.Name, generateRepo.Name); err != nil { | 	if err = CheckInitRepository(ctx, owner.Name, generateRepo.Name); err != nil { | ||||||
| 		return generateRepo, err | 		return generateRepo, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -108,12 +108,7 @@ done | |||||||
| } | } | ||||||
|  |  | ||||||
| // CreateDelegateHooks creates all the hooks scripts for the repo | // CreateDelegateHooks creates all the hooks scripts for the repo | ||||||
| func CreateDelegateHooks(repoPath string) error { | func CreateDelegateHooks(repoPath string) (err error) { | ||||||
| 	return createDelegateHooks(repoPath) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // createDelegateHooks creates all the hooks scripts for the repo |  | ||||||
| func createDelegateHooks(repoPath string) (err error) { |  | ||||||
| 	hookNames, hookTpls, giteaHookTpls := getHookTemplates() | 	hookNames, hookTpls, giteaHookTpls := getHookTemplates() | ||||||
| 	hookDir := filepath.Join(repoPath, "hooks") | 	hookDir := filepath.Join(repoPath, "hooks") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ | |||||||
| package repository | package repository | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| @@ -21,7 +20,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/options" | 	"code.gitea.io/gitea/modules/options" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/templates/vars" |  | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	asymkey_service "code.gitea.io/gitea/services/asymkey" | 	asymkey_service "code.gitea.io/gitea/services/asymkey" | ||||||
| ) | ) | ||||||
| @@ -126,95 +124,8 @@ func LoadRepoConfig() error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { | // InitRepoCommit temporarily changes with work directory. | ||||||
| 	commitTimeStr := time.Now().Format(time.RFC3339) | func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) { | ||||||
| 	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. |  | ||||||
| 	if stdout, _, err := git.NewCommand(ctx, "clone").AddDynamicArguments(repoPath, tmpDir). |  | ||||||
| 		SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)). |  | ||||||
| 		RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil { |  | ||||||
| 		log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) |  | ||||||
| 		return fmt.Errorf("git clone: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// README |  | ||||||
| 	data, err := options.Readme(opts.Readme) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	cloneLink := repo.CloneLink() |  | ||||||
| 	match := map[string]string{ |  | ||||||
| 		"Name":           repo.Name, |  | ||||||
| 		"Description":    repo.Description, |  | ||||||
| 		"CloneURL.SSH":   cloneLink.SSH, |  | ||||||
| 		"CloneURL.HTTPS": cloneLink.HTTPS, |  | ||||||
| 		"OwnerName":      repo.OwnerName, |  | ||||||
| 	} |  | ||||||
| 	res, err := vars.Expand(string(data), match) |  | ||||||
| 	if err != nil { |  | ||||||
| 		// here we could just log the error and continue the rendering |  | ||||||
| 		log.Error("unable to expand template vars for repo README: %s, err: %v", opts.Readme, err) |  | ||||||
| 	} |  | ||||||
| 	if err = os.WriteFile(filepath.Join(tmpDir, "README.md"), |  | ||||||
| 		[]byte(res), 0o644); err != nil { |  | ||||||
| 		return fmt.Errorf("write README.md: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// .gitignore |  | ||||||
| 	if len(opts.Gitignores) > 0 { |  | ||||||
| 		var buf bytes.Buffer |  | ||||||
| 		names := strings.Split(opts.Gitignores, ",") |  | ||||||
| 		for _, name := range names { |  | ||||||
| 			data, err = options.Gitignore(name) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) |  | ||||||
| 			} |  | ||||||
| 			buf.WriteString("# ---> " + name + "\n") |  | ||||||
| 			buf.Write(data) |  | ||||||
| 			buf.WriteString("\n") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if buf.Len() > 0 { |  | ||||||
| 			if err = os.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0o644); err != nil { |  | ||||||
| 				return fmt.Errorf("write .gitignore: %w", err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// LICENSE |  | ||||||
| 	if len(opts.License) > 0 { |  | ||||||
| 		data, err = getLicense(opts.License, &licenseValues{ |  | ||||||
| 			Owner: repo.OwnerName, |  | ||||||
| 			Email: authorSig.Email, |  | ||||||
| 			Repo:  repo.Name, |  | ||||||
| 			Year:  time.Now().Format("2006"), |  | ||||||
| 		}) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("getLicense[%s]: %w", opts.License, err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if err = os.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0o644); err != nil { |  | ||||||
| 			return fmt.Errorf("write LICENSE: %w", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // initRepoCommit temporarily changes with work directory. |  | ||||||
| func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) { |  | ||||||
| 	commitTimeStr := time.Now().Format(time.RFC3339) | 	commitTimeStr := time.Now().Format(time.RFC3339) | ||||||
|  |  | ||||||
| 	sig := u.NewGitSig() | 	sig := u.NewGitSig() | ||||||
| @@ -277,7 +188,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkInitRepository(ctx context.Context, owner, name string) (err error) { | func CheckInitRepository(ctx context.Context, owner, name string) (err error) { | ||||||
| 	// Somehow the directory could exist. | 	// Somehow the directory could exist. | ||||||
| 	repoPath := repo_model.RepoPath(owner, name) | 	repoPath := repo_model.RepoPath(owner, name) | ||||||
| 	isExist, err := util.IsExist(repoPath) | 	isExist, err := util.IsExist(repoPath) | ||||||
| @@ -295,77 +206,12 @@ func checkInitRepository(ctx context.Context, owner, name string) (err error) { | |||||||
| 	// Init git bare new repository. | 	// Init git bare new repository. | ||||||
| 	if err = git.InitRepository(ctx, repoPath, true); err != nil { | 	if err = git.InitRepository(ctx, repoPath, true); err != nil { | ||||||
| 		return fmt.Errorf("git.InitRepository: %w", err) | 		return fmt.Errorf("git.InitRepository: %w", err) | ||||||
| 	} else if err = createDelegateHooks(repoPath); err != nil { | 	} else if err = CreateDelegateHooks(repoPath); err != nil { | ||||||
| 		return fmt.Errorf("createDelegateHooks: %w", err) | 		return fmt.Errorf("createDelegateHooks: %w", err) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // InitRepository initializes README and .gitignore if needed. |  | ||||||
| func initRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) { |  | ||||||
| 	if err = checkInitRepository(ctx, repo.OwnerName, repo.Name); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Initialize repository according to user's choice. |  | ||||||
| 	if opts.AutoInit { |  | ||||||
| 		tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.RepoPath(), err) |  | ||||||
| 		} |  | ||||||
| 		defer func() { |  | ||||||
| 			if err := util.RemoveAll(tmpDir); err != nil { |  | ||||||
| 				log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err) |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
|  |  | ||||||
| 		if err = prepareRepoCommit(ctx, repo, tmpDir, repoPath, opts); err != nil { |  | ||||||
| 			return fmt.Errorf("prepareRepoCommit: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Apply changes and commit. |  | ||||||
| 		if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil { |  | ||||||
| 			return fmt.Errorf("initRepoCommit: %w", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Re-fetch the repository from database before updating it (else it would |  | ||||||
| 	// override changes that were done earlier with sql) |  | ||||||
| 	if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { |  | ||||||
| 		return fmt.Errorf("getRepositoryByID: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if !opts.AutoInit { |  | ||||||
| 		repo.IsEmpty = true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	repo.DefaultBranch = setting.Repository.DefaultBranch |  | ||||||
|  |  | ||||||
| 	if len(opts.DefaultBranch) > 0 { |  | ||||||
| 		repo.DefaultBranch = opts.DefaultBranch |  | ||||||
| 		gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("openRepository: %w", err) |  | ||||||
| 		} |  | ||||||
| 		defer gitRepo.Close() |  | ||||||
| 		if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { |  | ||||||
| 			return fmt.Errorf("setDefaultBranch: %w", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !repo.IsEmpty { |  | ||||||
| 			if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil { |  | ||||||
| 				return fmt.Errorf("SyncRepoBranches: %w", err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err = UpdateRepository(ctx, repo, false); err != nil { |  | ||||||
| 		return fmt.Errorf("updateRepository: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // InitializeLabels adds a label set to a repository using a template | // InitializeLabels adds a label set to a repository using a template | ||||||
| func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { | func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { | ||||||
| 	list, err := LoadTemplateLabelsByDisplayName(labelTemplate) | 	list, err := LoadTemplateLabelsByDisplayName(labelTemplate) | ||||||
|   | |||||||
| @@ -13,14 +13,14 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/options" | 	"code.gitea.io/gitea/modules/options" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type licenseValues struct { | type LicenseValues struct { | ||||||
| 	Owner string | 	Owner string | ||||||
| 	Email string | 	Email string | ||||||
| 	Repo  string | 	Repo  string | ||||||
| 	Year  string | 	Year  string | ||||||
| } | } | ||||||
|  |  | ||||||
| func getLicense(name string, values *licenseValues) ([]byte, error) { | func GetLicense(name string, values *LicenseValues) ([]byte, error) { | ||||||
| 	data, err := options.License(name) | 	data, err := options.License(name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) | 		return nil, fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) | ||||||
| @@ -28,7 +28,7 @@ func getLicense(name string, values *licenseValues) ([]byte, error) { | |||||||
| 	return fillLicensePlaceholder(name, values, data), nil | 	return fillLicensePlaceholder(name, values, data), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func fillLicensePlaceholder(name string, values *licenseValues, origin []byte) []byte { | func fillLicensePlaceholder(name string, values *LicenseValues, origin []byte) []byte { | ||||||
| 	placeholder := getLicensePlaceholder(name) | 	placeholder := getLicensePlaceholder(name) | ||||||
|  |  | ||||||
| 	scanner := bufio.NewScanner(bytes.NewReader(origin)) | 	scanner := bufio.NewScanner(bytes.NewReader(origin)) | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import ( | |||||||
| func Test_getLicense(t *testing.T) { | func Test_getLicense(t *testing.T) { | ||||||
| 	type args struct { | 	type args struct { | ||||||
| 		name   string | 		name   string | ||||||
| 		values *licenseValues | 		values *LicenseValues | ||||||
| 	} | 	} | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name    string | 		name    string | ||||||
| @@ -25,7 +25,7 @@ func Test_getLicense(t *testing.T) { | |||||||
| 			name: "regular", | 			name: "regular", | ||||||
| 			args: args{ | 			args: args{ | ||||||
| 				name:   "MIT", | 				name:   "MIT", | ||||||
| 				values: &licenseValues{Owner: "Gitea", Year: "2023"}, | 				values: &LicenseValues{Owner: "Gitea", Year: "2023"}, | ||||||
| 			}, | 			}, | ||||||
| 			want: `MIT License | 			want: `MIT License | ||||||
|  |  | ||||||
| @@ -49,11 +49,11 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||||||
| 	} | 	} | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
| 		t.Run(tt.name, func(t *testing.T) { | 		t.Run(tt.name, func(t *testing.T) { | ||||||
| 			got, err := getLicense(tt.args.name, tt.args.values) | 			got, err := GetLicense(tt.args.name, tt.args.values) | ||||||
| 			if !tt.wantErr(t, err, fmt.Sprintf("getLicense(%v, %v)", tt.args.name, tt.args.values)) { | 			if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.args.name, tt.args.values)) { | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			assert.Equalf(t, tt.want, string(got), "getLicense(%v, %v)", tt.args.name, tt.args.values) | 			assert.Equalf(t, tt.want, string(got), "GetLicense(%v, %v)", tt.args.name, tt.args.values) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -61,7 +61,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||||||
| func Test_fillLicensePlaceholder(t *testing.T) { | func Test_fillLicensePlaceholder(t *testing.T) { | ||||||
| 	type args struct { | 	type args struct { | ||||||
| 		name   string | 		name   string | ||||||
| 		values *licenseValues | 		values *LicenseValues | ||||||
| 		origin string | 		origin string | ||||||
| 	} | 	} | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| @@ -73,7 +73,7 @@ func Test_fillLicensePlaceholder(t *testing.T) { | |||||||
| 			name: "owner", | 			name: "owner", | ||||||
| 			args: args{ | 			args: args{ | ||||||
| 				name:   "regular", | 				name:   "regular", | ||||||
| 				values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | 				values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | ||||||
| 				origin: ` | 				origin: ` | ||||||
| <name of author> | <name of author> | ||||||
| <owner> | <owner> | ||||||
| @@ -104,7 +104,7 @@ Gitea | |||||||
| 			name: "email", | 			name: "email", | ||||||
| 			args: args{ | 			args: args{ | ||||||
| 				name:   "regular", | 				name:   "regular", | ||||||
| 				values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | 				values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | ||||||
| 				origin: ` | 				origin: ` | ||||||
| [EMAIL] | [EMAIL] | ||||||
| `, | `, | ||||||
| @@ -117,7 +117,7 @@ teabot@gitea.io | |||||||
| 			name: "repo", | 			name: "repo", | ||||||
| 			args: args{ | 			args: args{ | ||||||
| 				name:   "regular", | 				name:   "regular", | ||||||
| 				values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | 				values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | ||||||
| 				origin: ` | 				origin: ` | ||||||
| <program> | <program> | ||||||
| <one line to give the program's name and a brief idea of what it does.> | <one line to give the program's name and a brief idea of what it does.> | ||||||
| @@ -132,7 +132,7 @@ gitea | |||||||
| 			name: "year", | 			name: "year", | ||||||
| 			args: args{ | 			args: args{ | ||||||
| 				name:   "regular", | 				name:   "regular", | ||||||
| 				values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | 				values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | ||||||
| 				origin: ` | 				origin: ` | ||||||
| <year> | <year> | ||||||
| [YEAR] | [YEAR] | ||||||
| @@ -155,7 +155,7 @@ gitea | |||||||
| 			name: "0BSD", | 			name: "0BSD", | ||||||
| 			args: args{ | 			args: args{ | ||||||
| 				name:   "0BSD", | 				name:   "0BSD", | ||||||
| 				values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | 				values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, | ||||||
| 				origin: ` | 				origin: ` | ||||||
| Copyright (C) YEAR by AUTHOR EMAIL | Copyright (C) YEAR by AUTHOR EMAIL | ||||||
|  |  | ||||||
|   | |||||||
| @@ -256,11 +256,11 @@ func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error { | |||||||
| // CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. | // CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. | ||||||
| func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) { | func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) { | ||||||
| 	repoPath := repo.RepoPath() | 	repoPath := repo.RepoPath() | ||||||
| 	if err := createDelegateHooks(repoPath); err != nil { | 	if err := CreateDelegateHooks(repoPath); err != nil { | ||||||
| 		return repo, fmt.Errorf("createDelegateHooks: %w", err) | 		return repo, fmt.Errorf("createDelegateHooks: %w", err) | ||||||
| 	} | 	} | ||||||
| 	if repo.HasWiki() { | 	if repo.HasWiki() { | ||||||
| 		if err := createDelegateHooks(repo.WikiPath()); err != nil { | 		if err := CreateDelegateHooks(repo.WikiPath()); err != nil { | ||||||
| 			return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err) | 			return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ import ( | |||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/utils" | 	"code.gitea.io/gitea/routers/api/v1/utils" | ||||||
| 	repo_service "code.gitea.io/gitea/services/repository" | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| @@ -109,7 +108,7 @@ func AdoptRepository(ctx *context.APIContext) { | |||||||
| 		ctx.NotFound() | 		ctx.NotFound() | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ | 	if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{ | ||||||
| 		Name:      repoName, | 		Name:      repoName, | ||||||
| 		IsPrivate: true, | 		IsPrivate: true, | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
|   | |||||||
| @@ -22,7 +22,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/lfs" | 	"code.gitea.io/gitea/modules/lfs" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	base "code.gitea.io/gitea/modules/migration" | 	base "code.gitea.io/gitea/modules/migration" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| @@ -31,6 +30,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/services/forms" | 	"code.gitea.io/gitea/services/forms" | ||||||
| 	"code.gitea.io/gitea/services/migrations" | 	"code.gitea.io/gitea/services/migrations" | ||||||
| 	notify_service "code.gitea.io/gitea/services/notify" | 	notify_service "code.gitea.io/gitea/services/notify" | ||||||
|  | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Migrate migrate remote git repository to gitea | // Migrate migrate remote git repository to gitea | ||||||
| @@ -170,7 +170,7 @@ func Migrate(ctx *context.APIContext) { | |||||||
| 		opts.Releases = false | 		opts.Releases = false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	repo, err := repo_module.CreateRepository(ctx.Doer, repoOwner, repo_module.CreateRepoOptions{ | 	repo, err := repo_service.CreateRepositoryDirectly(ctx.Doer, repoOwner, repo_service.CreateRepoOptions{ | ||||||
| 		Name:           opts.RepoName, | 		Name:           opts.RepoName, | ||||||
| 		Description:    opts.Description, | 		Description:    opts.Description, | ||||||
| 		OriginalURL:    form.CloneAddr, | 		OriginalURL:    form.CloneAddr, | ||||||
|   | |||||||
| @@ -240,7 +240,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_module.CreateRepoOptions{ | 	repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_service.CreateRepoOptions{ | ||||||
| 		Name:          opt.Name, | 		Name:          opt.Name, | ||||||
| 		Description:   opt.Description, | 		Description:   opt.Description, | ||||||
| 		IssueLabels:   opt.IssueLabels, | 		IssueLabels:   opt.IssueLabels, | ||||||
|   | |||||||
| @@ -14,7 +14,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/routers/web/explore" | 	"code.gitea.io/gitea/routers/web/explore" | ||||||
| @@ -144,7 +143,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { | |||||||
| 	if has || !isDir { | 	if has || !isDir { | ||||||
| 		// Fallthrough to failure mode | 		// Fallthrough to failure mode | ||||||
| 	} else if action == "adopt" { | 	} else if action == "adopt" { | ||||||
| 		if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ | 		if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{ | ||||||
| 			Name:      dirSplit[1], | 			Name:      dirSplit[1], | ||||||
| 			IsPrivate: true, | 			IsPrivate: true, | ||||||
| 		}); err != nil { | 		}); err != nil { | ||||||
|   | |||||||
| @@ -275,7 +275,7 @@ func CreatePost(ctx *context.Context) { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ | 		repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{ | ||||||
| 			Name:          form.RepoName, | 			Name:          form.RepoName, | ||||||
| 			Description:   form.Description, | 			Description:   form.Description, | ||||||
| 			Gitignores:    form.Gitignores, | 			Gitignores:    form.Gitignores, | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ import ( | |||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	repo_service "code.gitea.io/gitea/services/repository" | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| @@ -45,7 +44,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { | |||||||
| 	if has || !isDir { | 	if has || !isDir { | ||||||
| 		// Fallthrough to failure mode | 		// Fallthrough to failure mode | ||||||
| 	} else if action == "adopt" && allowAdopt { | 	} else if action == "adopt" && allowAdopt { | ||||||
| 		if _, err := repo_service.AdoptRepository(ctx, ctxUser, ctxUser, repo_module.CreateRepoOptions{ | 		if _, err := repo_service.AdoptRepository(ctx, ctxUser, ctxUser, repo_service.CreateRepoOptions{ | ||||||
| 			Name:      dir, | 			Name:      dir, | ||||||
| 			IsPrivate: true, | 			IsPrivate: true, | ||||||
| 		}); err != nil { | 		}); err != nil { | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/uri" | 	"code.gitea.io/gitea/modules/uri" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/services/pull" | 	"code.gitea.io/gitea/services/pull" | ||||||
|  | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
|  |  | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| ) | ) | ||||||
| @@ -99,7 +100,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate | |||||||
|  |  | ||||||
| 	var r *repo_model.Repository | 	var r *repo_model.Repository | ||||||
| 	if opts.MigrateToRepoID <= 0 { | 	if opts.MigrateToRepoID <= 0 { | ||||||
| 		r, err = repo_module.CreateRepository(g.doer, owner, repo_module.CreateRepoOptions{ | 		r, err = repo_service.CreateRepositoryDirectly(g.doer, owner, repo_service.CreateRepoOptions{ | ||||||
| 			Name:           g.repoName, | 			Name:           g.repoName, | ||||||
| 			Description:    repo.Description, | 			Description:    repo.Description, | ||||||
| 			OriginalURL:    repo.OriginalURL, | 			OriginalURL:    repo.OriginalURL, | ||||||
|   | |||||||
| @@ -19,10 +19,10 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| 	cargo_module "code.gitea.io/gitea/modules/packages/cargo" | 	cargo_module "code.gitea.io/gitea/modules/packages/cargo" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/structs" | 	"code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| 	files_service "code.gitea.io/gitea/services/repository/files" | 	files_service "code.gitea.io/gitea/services/repository/files" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -206,7 +206,7 @@ func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.Use | |||||||
| 	repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName) | 	repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, util.ErrNotExist) { | 		if errors.Is(err, util.ErrNotExist) { | ||||||
| 			repo, err = repo_module.CreateRepository(doer, owner, repo_module.CreateRepoOptions{ | 			repo, err = repo_service.CreateRepositoryDirectly(doer, owner, repo_service.CreateRepoOptions{ | ||||||
| 				Name: IndexRepositoryName, | 				Name: IndexRepositoryName, | ||||||
| 			}) | 			}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| // AdoptRepository adopts pre-existing repository files for the user/organization. | // AdoptRepository adopts pre-existing repository files for the user/organization. | ||||||
| func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { | func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { | ||||||
| 	if !doer.IsAdmin && !u.CanCreateRepo() { | 	if !doer.IsAdmin && !u.CanCreateRepo() { | ||||||
| 		return nil, repo_model.ErrReachLimitOfRepo{ | 		return nil, repo_model.ErrReachLimitOfRepo{ | ||||||
| 			Limit: u.MaxRepoCreation, | 			Limit: u.MaxRepoCreation, | ||||||
|   | |||||||
							
								
								
									
										315
									
								
								services/repository/create.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								services/repository/create.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,315 @@ | |||||||
|  | // Copyright 2023 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package repository | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/models/db" | ||||||
|  | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
|  | 	user_model "code.gitea.io/gitea/models/user" | ||||||
|  | 	"code.gitea.io/gitea/modules/git" | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/options" | ||||||
|  | 	repo_module "code.gitea.io/gitea/modules/repository" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | 	api "code.gitea.io/gitea/modules/structs" | ||||||
|  | 	"code.gitea.io/gitea/modules/templates/vars" | ||||||
|  | 	"code.gitea.io/gitea/modules/util" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // CreateRepoOptions contains the create repository options | ||||||
|  | type CreateRepoOptions struct { | ||||||
|  | 	Name           string | ||||||
|  | 	Description    string | ||||||
|  | 	OriginalURL    string | ||||||
|  | 	GitServiceType api.GitServiceType | ||||||
|  | 	Gitignores     string | ||||||
|  | 	IssueLabels    string | ||||||
|  | 	License        string | ||||||
|  | 	Readme         string | ||||||
|  | 	DefaultBranch  string | ||||||
|  | 	IsPrivate      bool | ||||||
|  | 	IsMirror       bool | ||||||
|  | 	IsTemplate     bool | ||||||
|  | 	AutoInit       bool | ||||||
|  | 	Status         repo_model.RepositoryStatus | ||||||
|  | 	TrustModel     repo_model.TrustModelType | ||||||
|  | 	MirrorInterval string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { | ||||||
|  | 	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. | ||||||
|  | 	if stdout, _, err := git.NewCommand(ctx, "clone").AddDynamicArguments(repoPath, tmpDir). | ||||||
|  | 		SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)). | ||||||
|  | 		RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil { | ||||||
|  | 		log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) | ||||||
|  | 		return fmt.Errorf("git clone: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// README | ||||||
|  | 	data, err := options.Readme(opts.Readme) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cloneLink := repo.CloneLink() | ||||||
|  | 	match := map[string]string{ | ||||||
|  | 		"Name":           repo.Name, | ||||||
|  | 		"Description":    repo.Description, | ||||||
|  | 		"CloneURL.SSH":   cloneLink.SSH, | ||||||
|  | 		"CloneURL.HTTPS": cloneLink.HTTPS, | ||||||
|  | 		"OwnerName":      repo.OwnerName, | ||||||
|  | 	} | ||||||
|  | 	res, err := vars.Expand(string(data), match) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// here we could just log the error and continue the rendering | ||||||
|  | 		log.Error("unable to expand template vars for repo README: %s, err: %v", opts.Readme, err) | ||||||
|  | 	} | ||||||
|  | 	if err = os.WriteFile(filepath.Join(tmpDir, "README.md"), | ||||||
|  | 		[]byte(res), 0o644); err != nil { | ||||||
|  | 		return fmt.Errorf("write README.md: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// .gitignore | ||||||
|  | 	if len(opts.Gitignores) > 0 { | ||||||
|  | 		var buf bytes.Buffer | ||||||
|  | 		names := strings.Split(opts.Gitignores, ",") | ||||||
|  | 		for _, name := range names { | ||||||
|  | 			data, err = options.Gitignore(name) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) | ||||||
|  | 			} | ||||||
|  | 			buf.WriteString("# ---> " + name + "\n") | ||||||
|  | 			buf.Write(data) | ||||||
|  | 			buf.WriteString("\n") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if buf.Len() > 0 { | ||||||
|  | 			if err = os.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0o644); err != nil { | ||||||
|  | 				return fmt.Errorf("write .gitignore: %w", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// LICENSE | ||||||
|  | 	if len(opts.License) > 0 { | ||||||
|  | 		data, err = repo_module.GetLicense(opts.License, &repo_module.LicenseValues{ | ||||||
|  | 			Owner: repo.OwnerName, | ||||||
|  | 			Email: authorSig.Email, | ||||||
|  | 			Repo:  repo.Name, | ||||||
|  | 			Year:  time.Now().Format("2006"), | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("getLicense[%s]: %w", opts.License, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err = os.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0o644); err != nil { | ||||||
|  | 			return fmt.Errorf("write LICENSE: %w", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // InitRepository initializes README and .gitignore if needed. | ||||||
|  | func initRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) { | ||||||
|  | 	if err = repo_module.CheckInitRepository(ctx, repo.OwnerName, repo.Name); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Initialize repository according to user's choice. | ||||||
|  | 	if opts.AutoInit { | ||||||
|  | 		tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.RepoPath(), err) | ||||||
|  | 		} | ||||||
|  | 		defer func() { | ||||||
|  | 			if err := util.RemoveAll(tmpDir); err != nil { | ||||||
|  | 				log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err) | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  |  | ||||||
|  | 		if err = prepareRepoCommit(ctx, repo, tmpDir, repoPath, opts); err != nil { | ||||||
|  | 			return fmt.Errorf("prepareRepoCommit: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Apply changes and commit. | ||||||
|  | 		if err = repo_module.InitRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil { | ||||||
|  | 			return fmt.Errorf("initRepoCommit: %w", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Re-fetch the repository from database before updating it (else it would | ||||||
|  | 	// override changes that were done earlier with sql) | ||||||
|  | 	if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { | ||||||
|  | 		return fmt.Errorf("getRepositoryByID: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !opts.AutoInit { | ||||||
|  | 		repo.IsEmpty = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	repo.DefaultBranch = setting.Repository.DefaultBranch | ||||||
|  |  | ||||||
|  | 	if len(opts.DefaultBranch) > 0 { | ||||||
|  | 		repo.DefaultBranch = opts.DefaultBranch | ||||||
|  | 		gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("openRepository: %w", err) | ||||||
|  | 		} | ||||||
|  | 		defer gitRepo.Close() | ||||||
|  | 		if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { | ||||||
|  | 			return fmt.Errorf("setDefaultBranch: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !repo.IsEmpty { | ||||||
|  | 			if _, err := repo_module.SyncRepoBranches(ctx, repo.ID, u.ID); err != nil { | ||||||
|  | 				return fmt.Errorf("SyncRepoBranches: %w", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = UpdateRepository(ctx, repo, false); err != nil { | ||||||
|  | 		return fmt.Errorf("updateRepository: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CreateRepositoryDirectly creates a repository for the user/organization. | ||||||
|  | func CreateRepositoryDirectly(doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { | ||||||
|  | 	if !doer.IsAdmin && !u.CanCreateRepo() { | ||||||
|  | 		return nil, repo_model.ErrReachLimitOfRepo{ | ||||||
|  | 			Limit: u.MaxRepoCreation, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(opts.DefaultBranch) == 0 { | ||||||
|  | 		opts.DefaultBranch = setting.Repository.DefaultBranch | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check if label template exist | ||||||
|  | 	if len(opts.IssueLabels) > 0 { | ||||||
|  | 		if _, err := repo_module.LoadTemplateLabelsByDisplayName(opts.IssueLabels); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	repo := &repo_model.Repository{ | ||||||
|  | 		OwnerID:                         u.ID, | ||||||
|  | 		Owner:                           u, | ||||||
|  | 		OwnerName:                       u.Name, | ||||||
|  | 		Name:                            opts.Name, | ||||||
|  | 		LowerName:                       strings.ToLower(opts.Name), | ||||||
|  | 		Description:                     opts.Description, | ||||||
|  | 		OriginalURL:                     opts.OriginalURL, | ||||||
|  | 		OriginalServiceType:             opts.GitServiceType, | ||||||
|  | 		IsPrivate:                       opts.IsPrivate, | ||||||
|  | 		IsFsckEnabled:                   !opts.IsMirror, | ||||||
|  | 		IsTemplate:                      opts.IsTemplate, | ||||||
|  | 		CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, | ||||||
|  | 		Status:                          opts.Status, | ||||||
|  | 		IsEmpty:                         !opts.AutoInit, | ||||||
|  | 		TrustModel:                      opts.TrustModel, | ||||||
|  | 		IsMirror:                        opts.IsMirror, | ||||||
|  | 		DefaultBranch:                   opts.DefaultBranch, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var rollbackRepo *repo_model.Repository | ||||||
|  |  | ||||||
|  | 	if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { | ||||||
|  | 		if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// No need for init mirror. | ||||||
|  | 		if opts.IsMirror { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		repoPath := repo_model.RepoPath(u.Name, repo.Name) | ||||||
|  | 		isExist, err := util.IsExist(repoPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("Unable to check if %s exists. Error: %v", repoPath, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if isExist { | ||||||
|  | 			// repo already exists - We have two or three options. | ||||||
|  | 			// 1. We fail stating that the directory exists | ||||||
|  | 			// 2. We create the db repository to go with this data and adopt the git repo | ||||||
|  | 			// 3. We delete it and start afresh | ||||||
|  | 			// | ||||||
|  | 			// Previously Gitea would just delete and start afresh - this was naughty. | ||||||
|  | 			// So we will now fail and delegate to other functionality to adopt or delete | ||||||
|  | 			log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath) | ||||||
|  | 			return repo_model.ErrRepoFilesAlreadyExist{ | ||||||
|  | 				Uname: u.Name, | ||||||
|  | 				Name:  repo.Name, | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err = initRepository(ctx, repoPath, doer, repo, opts); err != nil { | ||||||
|  | 			if err2 := util.RemoveAll(repoPath); err2 != nil { | ||||||
|  | 				log.Error("initRepository: %v", err) | ||||||
|  | 				return fmt.Errorf( | ||||||
|  | 					"delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) | ||||||
|  | 			} | ||||||
|  | 			return fmt.Errorf("initRepository: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Initialize Issue Labels if selected | ||||||
|  | 		if len(opts.IssueLabels) > 0 { | ||||||
|  | 			if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { | ||||||
|  | 				rollbackRepo = repo | ||||||
|  | 				rollbackRepo.OwnerID = u.ID | ||||||
|  | 				return fmt.Errorf("InitializeLabels: %w", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { | ||||||
|  | 			return fmt.Errorf("checkDaemonExportOK: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if stdout, _, err := git.NewCommand(ctx, "update-server-info"). | ||||||
|  | 			SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). | ||||||
|  | 			RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { | ||||||
|  | 			log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) | ||||||
|  | 			rollbackRepo = repo | ||||||
|  | 			rollbackRepo.OwnerID = u.ID | ||||||
|  | 			return fmt.Errorf("CreateRepository(git update-server-info): %w", err) | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}); err != nil { | ||||||
|  | 		if rollbackRepo != nil { | ||||||
|  | 			if errDelete := models.DeleteRepository(doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil { | ||||||
|  | 				log.Error("Rollback deleteRepository: %v", errDelete) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return repo, nil | ||||||
|  | } | ||||||
							
								
								
									
										148
									
								
								services/repository/create_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								services/repository/create_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | |||||||
|  | // Copyright 2023 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package repository | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/models/db" | ||||||
|  | 	"code.gitea.io/gitea/models/organization" | ||||||
|  | 	"code.gitea.io/gitea/models/perm" | ||||||
|  | 	"code.gitea.io/gitea/models/unittest" | ||||||
|  | 	user_model "code.gitea.io/gitea/models/user" | ||||||
|  | 	"code.gitea.io/gitea/modules/structs" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestIncludesAllRepositoriesTeams(t *testing.T) { | ||||||
|  | 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||||
|  |  | ||||||
|  | 	testTeamRepositories := func(teamID int64, repoIds []int64) { | ||||||
|  | 		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) | ||||||
|  | 		assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name) | ||||||
|  | 		assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) | ||||||
|  | 		assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name) | ||||||
|  | 		for i, rid := range repoIds { | ||||||
|  | 			if rid > 0 { | ||||||
|  | 				assert.True(t, models.HasRepository(team, rid), "%s: HasRepository(%d) %d", rid, i) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Get an admin user. | ||||||
|  | 	user, err := user_model.GetUserByID(db.DefaultContext, 1) | ||||||
|  | 	assert.NoError(t, err, "GetUserByID") | ||||||
|  |  | ||||||
|  | 	// Create org. | ||||||
|  | 	org := &organization.Organization{ | ||||||
|  | 		Name:       "All_repo", | ||||||
|  | 		IsActive:   true, | ||||||
|  | 		Type:       user_model.UserTypeOrganization, | ||||||
|  | 		Visibility: structs.VisibleTypePublic, | ||||||
|  | 	} | ||||||
|  | 	assert.NoError(t, organization.CreateOrganization(org, user), "CreateOrganization") | ||||||
|  |  | ||||||
|  | 	// Check Owner team. | ||||||
|  | 	ownerTeam, err := org.GetOwnerTeam(db.DefaultContext) | ||||||
|  | 	assert.NoError(t, err, "GetOwnerTeam") | ||||||
|  | 	assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories") | ||||||
|  |  | ||||||
|  | 	// Create repos. | ||||||
|  | 	repoIds := make([]int64, 0) | ||||||
|  | 	for i := 0; i < 3; i++ { | ||||||
|  | 		r, err := CreateRepositoryDirectly(user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}) | ||||||
|  | 		assert.NoError(t, err, "CreateRepository %d", i) | ||||||
|  | 		if r != nil { | ||||||
|  | 			repoIds = append(repoIds, r.ID) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Get fresh copy of Owner team after creating repos. | ||||||
|  | 	ownerTeam, err = org.GetOwnerTeam(db.DefaultContext) | ||||||
|  | 	assert.NoError(t, err, "GetOwnerTeam") | ||||||
|  |  | ||||||
|  | 	// Create teams and check repositories. | ||||||
|  | 	teams := []*organization.Team{ | ||||||
|  | 		ownerTeam, | ||||||
|  | 		{ | ||||||
|  | 			OrgID:                   org.ID, | ||||||
|  | 			Name:                    "team one", | ||||||
|  | 			AccessMode:              perm.AccessModeRead, | ||||||
|  | 			IncludesAllRepositories: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			OrgID:                   org.ID, | ||||||
|  | 			Name:                    "team 2", | ||||||
|  | 			AccessMode:              perm.AccessModeRead, | ||||||
|  | 			IncludesAllRepositories: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			OrgID:                   org.ID, | ||||||
|  | 			Name:                    "team three", | ||||||
|  | 			AccessMode:              perm.AccessModeWrite, | ||||||
|  | 			IncludesAllRepositories: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			OrgID:                   org.ID, | ||||||
|  | 			Name:                    "team 4", | ||||||
|  | 			AccessMode:              perm.AccessModeWrite, | ||||||
|  | 			IncludesAllRepositories: false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	teamRepos := [][]int64{ | ||||||
|  | 		repoIds, | ||||||
|  | 		repoIds, | ||||||
|  | 		{}, | ||||||
|  | 		repoIds, | ||||||
|  | 		{}, | ||||||
|  | 	} | ||||||
|  | 	for i, team := range teams { | ||||||
|  | 		if i > 0 { // first team is Owner. | ||||||
|  | 			assert.NoError(t, models.NewTeam(team), "%s: NewTeam", team.Name) | ||||||
|  | 		} | ||||||
|  | 		testTeamRepositories(team.ID, teamRepos[i]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Update teams and check repositories. | ||||||
|  | 	teams[3].IncludesAllRepositories = false | ||||||
|  | 	teams[4].IncludesAllRepositories = true | ||||||
|  | 	teamRepos[4] = repoIds | ||||||
|  | 	for i, team := range teams { | ||||||
|  | 		assert.NoError(t, models.UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name) | ||||||
|  | 		testTeamRepositories(team.ID, teamRepos[i]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Create repo and check teams repositories. | ||||||
|  | 	r, err := CreateRepositoryDirectly(user, org.AsUser(), CreateRepoOptions{Name: "repo-last"}) | ||||||
|  | 	assert.NoError(t, err, "CreateRepository last") | ||||||
|  | 	if r != nil { | ||||||
|  | 		repoIds = append(repoIds, r.ID) | ||||||
|  | 	} | ||||||
|  | 	teamRepos[0] = repoIds | ||||||
|  | 	teamRepos[1] = repoIds | ||||||
|  | 	teamRepos[4] = repoIds | ||||||
|  | 	for i, team := range teams { | ||||||
|  | 		testTeamRepositories(team.ID, teamRepos[i]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Remove repo and check teams repositories. | ||||||
|  | 	assert.NoError(t, models.DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository") | ||||||
|  | 	teamRepos[0] = repoIds[1:] | ||||||
|  | 	teamRepos[1] = repoIds[1:] | ||||||
|  | 	teamRepos[3] = repoIds[1:3] | ||||||
|  | 	teamRepos[4] = repoIds[1:] | ||||||
|  | 	for i, team := range teams { | ||||||
|  | 		testTeamRepositories(team.ID, teamRepos[i]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Wipe created items. | ||||||
|  | 	for i, rid := range repoIds { | ||||||
|  | 		if i > 0 { // first repo already deleted. | ||||||
|  | 			assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") | ||||||
|  | } | ||||||
| @@ -40,8 +40,8 @@ type WebSearchResults struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| // CreateRepository creates a repository for the user/organization. | // CreateRepository creates a repository for the user/organization. | ||||||
| func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { | func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { | ||||||
| 	repo, err := repo_module.CreateRepository(doer, owner, opts) | 	repo, err := CreateRepositoryDirectly(doer, owner, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		// No need to rollback here we should do this in CreateRepository... | 		// No need to rollback here we should do this in CreateRepository... | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -84,7 +84,7 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	repo, err := CreateRepository(ctx, authUser, owner, repo_module.CreateRepoOptions{ | 	repo, err := CreateRepository(ctx, authUser, owner, CreateRepoOptions{ | ||||||
| 		Name:      repoName, | 		Name:      repoName, | ||||||
| 		IsPrivate: setting.Repository.DefaultPushCreatePrivate, | 		IsPrivate: setting.Repository.DefaultPushCreatePrivate, | ||||||
| 	}) | 	}) | ||||||
|   | |||||||
| @@ -14,12 +14,12 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	base "code.gitea.io/gitea/modules/migration" | 	base "code.gitea.io/gitea/modules/migration" | ||||||
| 	"code.gitea.io/gitea/modules/queue" | 	"code.gitea.io/gitea/modules/queue" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	"code.gitea.io/gitea/modules/secret" | 	"code.gitea.io/gitea/modules/secret" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/structs" | 	"code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // taskQueue is a global queue of tasks | // taskQueue is a global queue of tasks | ||||||
| @@ -100,7 +100,7 @@ func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*adm | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	repo, err := repo_module.CreateRepository(doer, u, repo_module.CreateRepoOptions{ | 	repo, err := repo_service.CreateRepositoryDirectly(doer, u, repo_service.CreateRepoOptions{ | ||||||
| 		Name:           opts.RepoName, | 		Name:           opts.RepoName, | ||||||
| 		Description:    opts.Description, | 		Description:    opts.Description, | ||||||
| 		OriginalURL:    opts.OriginalURL, | 		OriginalURL:    opts.OriginalURL, | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ import ( | |||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	actions_module "code.gitea.io/gitea/modules/actions" | 	actions_module "code.gitea.io/gitea/modules/actions" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	pull_service "code.gitea.io/gitea/services/pull" | 	pull_service "code.gitea.io/gitea/services/pull" | ||||||
| 	repo_service "code.gitea.io/gitea/services/repository" | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| 	files_service "code.gitea.io/gitea/services/repository/files" | 	files_service "code.gitea.io/gitea/services/repository/files" | ||||||
| @@ -32,7 +31,7 @@ func TestPullRequestTargetEvent(t *testing.T) { | |||||||
| 		user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the forked repo | 		user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the forked repo | ||||||
|  |  | ||||||
| 		// create the base repo | 		// create the base repo | ||||||
| 		baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_module.CreateRepoOptions{ | 		baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{ | ||||||
| 			Name:          "repo-pull-request-target", | 			Name:          "repo-pull-request-target", | ||||||
| 			Description:   "test pull-request-target event", | 			Description:   "test pull-request-target event", | ||||||
| 			AutoInit:      true, | 			AutoInit:      true, | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/repository" | 	"code.gitea.io/gitea/modules/repository" | ||||||
| 	mirror_service "code.gitea.io/gitea/services/mirror" | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
| 	release_service "code.gitea.io/gitea/services/release" | 	release_service "code.gitea.io/gitea/services/release" | ||||||
|  | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| 	"code.gitea.io/gitea/tests" | 	"code.gitea.io/gitea/tests" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| @@ -38,7 +39,7 @@ func TestMirrorPull(t *testing.T) { | |||||||
| 		Releases:    false, | 		Releases:    false, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mirrorRepo, err := repository.CreateRepository(user, user, repository.CreateRepoOptions{ | 	mirrorRepo, err := repo_service.CreateRepositoryDirectly(user, user, repo_service.CreateRepoOptions{ | ||||||
| 		Name:        opts.RepoName, | 		Name:        opts.RepoName, | ||||||
| 		Description: opts.Description, | 		Description: opts.Description, | ||||||
| 		IsPrivate:   opts.Private, | 		IsPrivate:   opts.Private, | ||||||
|   | |||||||
| @@ -17,10 +17,10 @@ import ( | |||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	gitea_context "code.gitea.io/gitea/modules/context" | 	gitea_context "code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/repository" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/services/migrations" | 	"code.gitea.io/gitea/services/migrations" | ||||||
| 	mirror_service "code.gitea.io/gitea/services/mirror" | 	mirror_service "code.gitea.io/gitea/services/mirror" | ||||||
|  | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| 	"code.gitea.io/gitea/tests" | 	"code.gitea.io/gitea/tests" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| @@ -39,7 +39,7 @@ func testMirrorPush(t *testing.T, u *url.URL) { | |||||||
| 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | ||||||
| 	srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) | 	srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) | ||||||
|  |  | ||||||
| 	mirrorRepo, err := repository.CreateRepository(user, user, repository.CreateRepoOptions{ | 	mirrorRepo, err := repo_service.CreateRepositoryDirectly(user, user, repo_service.CreateRepoOptions{ | ||||||
| 		Name: "test-push-mirror", | 		Name: "test-push-mirror", | ||||||
| 	}) | 	}) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
|   | |||||||
| @@ -25,7 +25,6 @@ import ( | |||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/models/webhook" | 	"code.gitea.io/gitea/models/webhook" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/test" | 	"code.gitea.io/gitea/modules/test" | ||||||
| 	"code.gitea.io/gitea/modules/translation" | 	"code.gitea.io/gitea/modules/translation" | ||||||
| @@ -356,7 +355,7 @@ func TestConflictChecking(t *testing.T) { | |||||||
| 		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | 		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | ||||||
|  |  | ||||||
| 		// Create new clean repo to test conflict checking. | 		// Create new clean repo to test conflict checking. | ||||||
| 		baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_module.CreateRepoOptions{ | 		baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ | ||||||
| 			Name:          "conflict-checking", | 			Name:          "conflict-checking", | ||||||
| 			Description:   "Tempo repo", | 			Description:   "Tempo repo", | ||||||
| 			AutoInit:      true, | 			AutoInit:      true, | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/unittest" | 	"code.gitea.io/gitea/models/unittest" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" |  | ||||||
| 	pull_service "code.gitea.io/gitea/services/pull" | 	pull_service "code.gitea.io/gitea/services/pull" | ||||||
| 	repo_service "code.gitea.io/gitea/services/repository" | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| 	files_service "code.gitea.io/gitea/services/repository/files" | 	files_service "code.gitea.io/gitea/services/repository/files" | ||||||
| @@ -81,7 +80,7 @@ func TestAPIPullUpdateByRebase(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest { | func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest { | ||||||
| 	baseRepo, err := repo_service.CreateRepository(db.DefaultContext, actor, actor, repo_module.CreateRepoOptions{ | 	baseRepo, err := repo_service.CreateRepository(db.DefaultContext, actor, actor, repo_service.CreateRepoOptions{ | ||||||
| 		Name:        "repo-pr-update", | 		Name:        "repo-pr-update", | ||||||
| 		Description: "repo-tmp-pr-update description", | 		Description: "repo-tmp-pr-update description", | ||||||
| 		AutoInit:    true, | 		AutoInit:    true, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user