code.gitea.io/gitea@v1.22.3/modules/repository/create.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repository
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"code.gitea.io/gitea/models"
    15  	activities_model "code.gitea.io/gitea/models/activities"
    16  	"code.gitea.io/gitea/models/db"
    17  	git_model "code.gitea.io/gitea/models/git"
    18  	"code.gitea.io/gitea/models/organization"
    19  	"code.gitea.io/gitea/models/perm"
    20  	access_model "code.gitea.io/gitea/models/perm/access"
    21  	repo_model "code.gitea.io/gitea/models/repo"
    22  	"code.gitea.io/gitea/models/unit"
    23  	user_model "code.gitea.io/gitea/models/user"
    24  	"code.gitea.io/gitea/models/webhook"
    25  	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
    26  	"code.gitea.io/gitea/modules/log"
    27  	"code.gitea.io/gitea/modules/setting"
    28  	api "code.gitea.io/gitea/modules/structs"
    29  	"code.gitea.io/gitea/modules/util"
    30  )
    31  
    32  // CreateRepositoryByExample creates a repository for the user/organization.
    33  func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt, isFork bool) (err error) {
    34  	if err = repo_model.IsUsableRepoName(repo.Name); err != nil {
    35  		return err
    36  	}
    37  
    38  	has, err := repo_model.IsRepositoryModelExist(ctx, u, repo.Name)
    39  	if err != nil {
    40  		return fmt.Errorf("IsRepositoryExist: %w", err)
    41  	} else if has {
    42  		return repo_model.ErrRepoAlreadyExist{
    43  			Uname: u.Name,
    44  			Name:  repo.Name,
    45  		}
    46  	}
    47  
    48  	repoPath := repo_model.RepoPath(u.Name, repo.Name)
    49  	isExist, err := util.IsExist(repoPath)
    50  	if err != nil {
    51  		log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
    52  		return err
    53  	}
    54  	if !overwriteOrAdopt && isExist {
    55  		log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
    56  		return repo_model.ErrRepoFilesAlreadyExist{
    57  			Uname: u.Name,
    58  			Name:  repo.Name,
    59  		}
    60  	}
    61  
    62  	if err = db.Insert(ctx, repo); err != nil {
    63  		return err
    64  	}
    65  	if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil {
    66  		return err
    67  	}
    68  
    69  	// insert units for repo
    70  	defaultUnits := unit.DefaultRepoUnits
    71  	if isFork {
    72  		defaultUnits = unit.DefaultForkRepoUnits
    73  	}
    74  	units := make([]repo_model.RepoUnit, 0, len(defaultUnits))
    75  	for _, tp := range defaultUnits {
    76  		if tp == unit.TypeIssues {
    77  			units = append(units, repo_model.RepoUnit{
    78  				RepoID: repo.ID,
    79  				Type:   tp,
    80  				Config: &repo_model.IssuesConfig{
    81  					EnableTimetracker:                setting.Service.DefaultEnableTimetracking,
    82  					AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
    83  					EnableDependencies:               setting.Service.DefaultEnableDependencies,
    84  				},
    85  			})
    86  		} else if tp == unit.TypePullRequests {
    87  			units = append(units, repo_model.RepoUnit{
    88  				RepoID: repo.ID,
    89  				Type:   tp,
    90  				Config: &repo_model.PullRequestsConfig{
    91  					AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true,
    92  					DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
    93  					AllowRebaseUpdate: true,
    94  				},
    95  			})
    96  		} else if tp == unit.TypeProjects {
    97  			units = append(units, repo_model.RepoUnit{
    98  				RepoID: repo.ID,
    99  				Type:   tp,
   100  				Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll},
   101  			})
   102  		} else {
   103  			units = append(units, repo_model.RepoUnit{
   104  				RepoID: repo.ID,
   105  				Type:   tp,
   106  			})
   107  		}
   108  	}
   109  
   110  	if err = db.Insert(ctx, units); err != nil {
   111  		return err
   112  	}
   113  
   114  	// Remember visibility preference.
   115  	u.LastRepoVisibility = repo.IsPrivate
   116  	if err = user_model.UpdateUserCols(ctx, u, "last_repo_visibility"); err != nil {
   117  		return fmt.Errorf("UpdateUserCols: %w", err)
   118  	}
   119  
   120  	if err = user_model.IncrUserRepoNum(ctx, u.ID); err != nil {
   121  		return fmt.Errorf("IncrUserRepoNum: %w", err)
   122  	}
   123  	u.NumRepos++
   124  
   125  	// Give access to all members in teams with access to all repositories.
   126  	if u.IsOrganization() {
   127  		teams, err := organization.FindOrgTeams(ctx, u.ID)
   128  		if err != nil {
   129  			return fmt.Errorf("FindOrgTeams: %w", err)
   130  		}
   131  		for _, t := range teams {
   132  			if t.IncludesAllRepositories {
   133  				if err := models.AddRepository(ctx, t, repo); err != nil {
   134  					return fmt.Errorf("AddRepository: %w", err)
   135  				}
   136  			}
   137  		}
   138  
   139  		if isAdmin, err := access_model.IsUserRepoAdmin(ctx, repo, doer); err != nil {
   140  			return fmt.Errorf("IsUserRepoAdmin: %w", err)
   141  		} else if !isAdmin {
   142  			// Make creator repo admin if it wasn't assigned automatically
   143  			if err = AddCollaborator(ctx, repo, doer); err != nil {
   144  				return fmt.Errorf("AddCollaborator: %w", err)
   145  			}
   146  			if err = repo_model.ChangeCollaborationAccessMode(ctx, repo, doer.ID, perm.AccessModeAdmin); err != nil {
   147  				return fmt.Errorf("ChangeCollaborationAccessModeCtx: %w", err)
   148  			}
   149  		}
   150  	} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
   151  		// Organization automatically called this in AddRepository method.
   152  		return fmt.Errorf("RecalculateAccesses: %w", err)
   153  	}
   154  
   155  	if setting.Service.AutoWatchNewRepos {
   156  		if err = repo_model.WatchRepo(ctx, doer, repo, true); err != nil {
   157  			return fmt.Errorf("WatchRepo: %w", err)
   158  		}
   159  	}
   160  
   161  	if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil {
   162  		return fmt.Errorf("CopyDefaultWebhooksToRepo: %w", err)
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
   169  
   170  // getDirectorySize returns the disk consumption for a given path
   171  func getDirectorySize(path string) (int64, error) {
   172  	var size int64
   173  	err := filepath.WalkDir(path, func(_ string, entry os.DirEntry, err error) error {
   174  		if os.IsNotExist(err) { // ignore the error because some files (like temp/lock file) may be deleted during traversing.
   175  			return nil
   176  		} else if err != nil {
   177  			return err
   178  		}
   179  		if entry.IsDir() {
   180  			return nil
   181  		}
   182  		info, err := entry.Info()
   183  		if os.IsNotExist(err) { // ignore the error as above
   184  			return nil
   185  		} else if err != nil {
   186  			return err
   187  		}
   188  		if (info.Mode() & notRegularFileMode) == 0 {
   189  			size += info.Size()
   190  		}
   191  		return nil
   192  	})
   193  	return size, err
   194  }
   195  
   196  // UpdateRepoSize updates the repository size, calculating it using getDirectorySize
   197  func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
   198  	size, err := getDirectorySize(repo.RepoPath())
   199  	if err != nil {
   200  		return fmt.Errorf("updateSize: %w", err)
   201  	}
   202  
   203  	lfsSize, err := git_model.GetRepoLFSSize(ctx, repo.ID)
   204  	if err != nil {
   205  		return fmt.Errorf("updateSize: GetLFSMetaObjects: %w", err)
   206  	}
   207  
   208  	return repo_model.UpdateRepoSize(ctx, repo.ID, size, lfsSize)
   209  }
   210  
   211  // CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
   212  func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error {
   213  	if err := repo.LoadOwner(ctx); err != nil {
   214  		return err
   215  	}
   216  
   217  	// Create/Remove git-daemon-export-ok for git-daemon...
   218  	daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
   219  
   220  	isExist, err := util.IsExist(daemonExportFile)
   221  	if err != nil {
   222  		log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
   223  		return err
   224  	}
   225  
   226  	isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic
   227  	if !isPublic && isExist {
   228  		if err = util.Remove(daemonExportFile); err != nil {
   229  			log.Error("Failed to remove %s: %v", daemonExportFile, err)
   230  		}
   231  	} else if isPublic && !isExist {
   232  		if f, err := os.Create(daemonExportFile); err != nil {
   233  			log.Error("Failed to create %s: %v", daemonExportFile, err)
   234  		} else {
   235  			f.Close()
   236  		}
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  // UpdateRepository updates a repository with db context
   243  func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) {
   244  	repo.LowerName = strings.ToLower(repo.Name)
   245  
   246  	e := db.GetEngine(ctx)
   247  
   248  	if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
   249  		return fmt.Errorf("update: %w", err)
   250  	}
   251  
   252  	if err = UpdateRepoSize(ctx, repo); err != nil {
   253  		log.Error("Failed to update size for repository: %v", err)
   254  	}
   255  
   256  	if visibilityChanged {
   257  		if err = repo.LoadOwner(ctx); err != nil {
   258  			return fmt.Errorf("LoadOwner: %w", err)
   259  		}
   260  		if repo.Owner.IsOrganization() {
   261  			// Organization repository need to recalculate access table when visibility is changed.
   262  			if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
   263  				return fmt.Errorf("recalculateTeamAccesses: %w", err)
   264  			}
   265  		}
   266  
   267  		// If repo has become private, we need to set its actions to private.
   268  		if repo.IsPrivate {
   269  			_, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{
   270  				IsPrivate: true,
   271  			})
   272  			if err != nil {
   273  				return err
   274  			}
   275  
   276  			if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
   277  				return err
   278  			}
   279  		}
   280  
   281  		// Create/Remove git-daemon-export-ok for git-daemon...
   282  		if err := CheckDaemonExportOK(ctx, repo); err != nil {
   283  			return err
   284  		}
   285  
   286  		forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
   287  		if err != nil {
   288  			return fmt.Errorf("getRepositoriesByForkID: %w", err)
   289  		}
   290  		for i := range forkRepos {
   291  			forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate
   292  			if err = UpdateRepository(ctx, forkRepos[i], true); err != nil {
   293  				return fmt.Errorf("updateRepository[%d]: %w", forkRepos[i].ID, err)
   294  			}
   295  		}
   296  
   297  		// If visibility is changed, we need to update the issue indexer.
   298  		// Since the data in the issue indexer have field to indicate if the repo is public or not.
   299  		issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
   300  	}
   301  
   302  	return nil
   303  }