Skip to content

Don't automatically delete repository files if they are present #12409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ PREFIX_ARCHIVE_FILES = true
DISABLE_MIRRORS = false
; The default branch name of new repositories
DEFAULT_BRANCH=master
; Allow adoption of unadopted repositories
ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES=false
; Allow overwrite of unadopted repositories
ALLOW_OVERWRITE_OF_UNADOPTED_REPOSITORIES=false

[repository.editor]
; List of file extensions for which lines should be wrapped in the Monaco editor
Expand Down
16 changes: 16 additions & 0 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,22 @@ func (err ErrRepoAlreadyExist) Error() string {
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
}

// ErrRepoFilesAlreadyExist represents a "RepoFilesAlreadyExist" kind of error.
type ErrRepoFilesAlreadyExist struct {
Uname string
Name string
}

// IsErrRepoFilesAlreadyExist checks if an error is a ErrRepoAlreadyExist.
func IsErrRepoFilesAlreadyExist(err error) bool {
_, ok := err.(ErrRepoFilesAlreadyExist)
return ok
}

func (err ErrRepoFilesAlreadyExist) Error() string {
return fmt.Sprintf("repository files already exist [uname: %s, name: %s]", err.Uname, err.Name)
}

// ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error.
type ErrForkAlreadyExist struct {
Uname string
Expand Down
49 changes: 32 additions & 17 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func (repo *Repository) IsBeingCreated() bool {
func (repo *Repository) AfterLoad() {
// FIXME: use models migration to solve all at once.
if len(repo.DefaultBranch) == 0 {
repo.DefaultBranch = "master"
repo.DefaultBranch = setting.Repository.DefaultBranch
}

repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
Expand Down Expand Up @@ -1048,7 +1048,7 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
}

// CheckCreateRepository check if could created a repository
func CheckCreateRepository(doer, u *User, name string) error {
func CheckCreateRepository(doer, u *User, name string, overwriteOrAdopt bool) error {
if !doer.CanCreateRepo() {
return ErrReachLimitOfRepo{u.MaxRepoCreation}
}
Expand All @@ -1063,25 +1063,31 @@ func CheckCreateRepository(doer, u *User, name string) error {
} else if has {
return ErrRepoAlreadyExist{u.Name, name}
}

if !overwriteOrAdopt && com.IsExist(RepoPath(u.Name, name)) {
return ErrRepoFilesAlreadyExist{u.Name, name}
}
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
AutoInit bool
Status RepositoryStatus
TrustModel TrustModelType
Name string
Description string
OriginalURL string
GitServiceType api.GitServiceType
Gitignores string
IssueLabels string
License string
Readme string
DefaultBranch string
IsPrivate bool
IsMirror bool
AutoInit bool
Status RepositoryStatus
TrustModel TrustModelType
AdoptPreExisting bool
OverwritePreExisting bool
}

// GetRepoInitFile returns repository init files
Expand Down Expand Up @@ -1120,7 +1126,7 @@ func IsUsableRepoName(name string) error {
}

// CreateRepository creates a repository for the user/organization.
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) {
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, overwriteOrAdopt bool) (err error) {
if err = IsUsableRepoName(repo.Name); err != nil {
return err
}
Expand All @@ -1132,6 +1138,15 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
return ErrRepoAlreadyExist{u.Name, repo.Name}
}

repoPath := RepoPath(u.Name, repo.Name)
if !overwriteOrAdopt && com.IsExist(repoPath) {
log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
return ErrRepoFilesAlreadyExist{
Uname: u.Name,
Name: repo.Name,
}
}

if _, err = ctx.e.Insert(repo); err != nil {
return err
}
Expand Down
19 changes: 10 additions & 9 deletions models/repo_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ import (

// GenerateRepoOptions contains the template units to generate
type GenerateRepoOptions struct {
Name string
Description string
Private bool
GitContent bool
Topics bool
GitHooks bool
Webhooks bool
Avatar bool
IssueLabels bool
Name string
Description string
Private bool
GitContent bool
Topics bool
GitHooks bool
Webhooks bool
Avatar bool
IssueLabels bool
OverwritePreExisting bool
}

// IsValid checks whether at least one option is chosen for generation
Expand Down
27 changes: 16 additions & 11 deletions modules/auth/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ type CreateRepoForm struct {
Webhooks bool
Avatar bool
Labels bool
TrustModel string

TrustModel string

AdoptPreExisting bool
OverwritePreExisting bool
}

// Validate validates the fields
Expand All @@ -65,16 +69,17 @@ type MigrateRepoForm struct {
// required: true
UID int64 `json:"uid" binding:"Required"`
// required: true
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
Uncyclo bool `json:"wiki"`
Milestones bool `json:"milestones"`
Labels bool `json:"labels"`
Issues bool `json:"issues"`
PullRequests bool `json:"pull_requests"`
Releases bool `json:"releases"`
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
Uncyclo bool `json:"wiki"`
Milestones bool `json:"milestones"`
Labels bool `json:"labels"`
Issues bool `json:"issues"`
PullRequests bool `json:"pull_requests"`
Releases bool `json:"releases"`
OverwritePreExisting bool `json:"overwrite_pre_existing"`
}

// Validate validates the fields
Expand Down
29 changes: 15 additions & 14 deletions modules/migrations/base/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@ type MigrateOptions struct {
// required: true
UID int `json:"uid" binding:"Required"`
// required: true
RepoName string `json:"repo_name" binding:"Required"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description"`
OriginalURL string
GitServiceType structs.GitServiceType
Uncyclo bool
Issues bool
Milestones bool
Labels bool
Releases bool
Comments bool
PullRequests bool
MigrateToRepoID int64
RepoName string `json:"repo_name" binding:"Required"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description"`
OriginalURL string
GitServiceType structs.GitServiceType
Uncyclo bool
Issues bool
Milestones bool
Labels bool
Releases bool
Comments bool
PullRequests bool
MigrateToRepoID int64
OverwritePreExisting bool `json:"overwrite_pre_existing"`
}
15 changes: 8 additions & 7 deletions modules/migrations/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,14 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
var r *models.Repository
if opts.MigrateToRepoID <= 0 {
r, err = repo_module.CreateRepository(g.doer, owner, models.CreateRepoOptions{
Name: g.repoName,
Description: repo.Description,
OriginalURL: repo.OriginalURL,
GitServiceType: opts.GitServiceType,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
Name: g.repoName,
Description: repo.Description,
OriginalURL: repo.OriginalURL,
GitServiceType: opts.GitServiceType,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
OverwritePreExisting: opts.OverwritePreExisting && (g.doer.IsAdmin || setting.Repository.AllowOverwriteOfUnadoptedRepositories),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why an admin could not follow the setting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? This allows Site Admins to explicitly overwrite pre-existing repositories.

})
} else {
r, err = models.GetRepositoryByID(opts.MigrateToRepoID)
Expand Down
85 changes: 68 additions & 17 deletions modules/repository/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

"github.com/unknwon/com"
)

// CreateRepository creates a repository for the user/organization.
func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *models.Repository, err error) {
func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*models.Repository, error) {
if !doer.IsAdmin && !u.CanCreateRepo() {
return nil, models.ErrReachLimitOfRepo{
Limit: u.MaxRepoCreation,
Expand Down Expand Up @@ -44,39 +46,88 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
TrustModel: opts.TrustModel,
}

err = models.WithTx(func(ctx models.DBContext) error {
if err = models.CreateRepository(ctx, doer, u, repo); err != nil {
overwriteOrAdopt := (!opts.IsMirror && opts.AdoptPreExisting && (doer.IsAdmin || setting.Repository.AllowAdoptionOfUnadoptedRepositories)) ||
(opts.OverwritePreExisting && (doer.IsAdmin || setting.Repository.AllowOverwriteOfUnadoptedRepositories))

if err := models.WithTx(func(ctx models.DBContext) error {
if err := models.CreateRepository(ctx, doer, u, repo, overwriteOrAdopt); err != nil {
return err
}

// No need for init mirror.
if !opts.IsMirror {
repoPath := models.RepoPath(u.Name, repo.Name)
if err = initRepository(ctx, repoPath, doer, repo, opts); err != nil {
if opts.IsMirror {
return nil
}

shouldInit := true

repoPath := models.RepoPath(u.Name, repo.Name)
if com.IsExist(repoPath) {
// 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.
if opts.AdoptPreExisting {
shouldInit = false
if err := adoptRepository(ctx, repoPath, doer, repo, opts); err != nil {
return fmt.Errorf("createDelegateHooks: %v", err)
}
} else if opts.OverwritePreExisting {
log.Warn("An already existing repository was deleted at %s", repoPath)
if err := util.RemoveAll(repoPath); err != nil {
log.Error("Unable to remove already existing repository at %s: Error: %v", repoPath, err)
return fmt.Errorf(
"unable to delete repo directory %s/%s: %v", u.Name, repo.Name, err)
}
} else {
log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
return models.ErrRepoFilesAlreadyExist{
Uname: u.Name,
Name: repo.Name,
}
}
}

if shouldInit {
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: %v", err)
}
}

// Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 {
if err = models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
return fmt.Errorf("InitializeLabels: %v", err)
// Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 {
if err := models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
if shouldInit {
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
}
return fmt.Errorf("InitializeLabels: %v", err)
}
}

if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunInDir(repoPath); err != nil {
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunInDir(repoPath); err != nil {
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
if shouldInit {
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
}
return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
}
return nil
})
}); err != nil {
return nil, err
}

return repo, err
return repo, nil
}
Loading