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 }