code.gitea.io/gitea@v1.21.7/services/repository/adopt.go (about) 1 // Copyright 2020 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/db" 15 git_model "code.gitea.io/gitea/models/git" 16 repo_model "code.gitea.io/gitea/models/repo" 17 user_model "code.gitea.io/gitea/models/user" 18 "code.gitea.io/gitea/modules/container" 19 "code.gitea.io/gitea/modules/git" 20 "code.gitea.io/gitea/modules/log" 21 repo_module "code.gitea.io/gitea/modules/repository" 22 "code.gitea.io/gitea/modules/setting" 23 "code.gitea.io/gitea/modules/util" 24 notify_service "code.gitea.io/gitea/services/notify" 25 26 "github.com/gobwas/glob" 27 ) 28 29 // AdoptRepository adopts pre-existing repository files for the user/organization. 30 func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { 31 if !doer.IsAdmin && !u.CanCreateRepo() { 32 return nil, repo_model.ErrReachLimitOfRepo{ 33 Limit: u.MaxRepoCreation, 34 } 35 } 36 37 if len(opts.DefaultBranch) == 0 { 38 opts.DefaultBranch = setting.Repository.DefaultBranch 39 } 40 41 repo := &repo_model.Repository{ 42 OwnerID: u.ID, 43 Owner: u, 44 OwnerName: u.Name, 45 Name: opts.Name, 46 LowerName: strings.ToLower(opts.Name), 47 Description: opts.Description, 48 OriginalURL: opts.OriginalURL, 49 OriginalServiceType: opts.GitServiceType, 50 IsPrivate: opts.IsPrivate, 51 IsFsckEnabled: !opts.IsMirror, 52 CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, 53 Status: opts.Status, 54 IsEmpty: !opts.AutoInit, 55 } 56 57 if err := db.WithTx(ctx, func(ctx context.Context) error { 58 repoPath := repo_model.RepoPath(u.Name, repo.Name) 59 isExist, err := util.IsExist(repoPath) 60 if err != nil { 61 log.Error("Unable to check if %s exists. Error: %v", repoPath, err) 62 return err 63 } 64 if !isExist { 65 return repo_model.ErrRepoNotExist{ 66 OwnerName: u.Name, 67 Name: repo.Name, 68 } 69 } 70 71 if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil { 72 return err 73 } 74 75 // Re-fetch the repository from database before updating it (else it would 76 // override changes that were done earlier with sql) 77 if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { 78 return fmt.Errorf("getRepositoryByID: %w", err) 79 } 80 81 if err := adoptRepository(ctx, repoPath, doer, repo, opts.DefaultBranch); err != nil { 82 return fmt.Errorf("createDelegateHooks: %w", err) 83 } 84 85 if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { 86 return fmt.Errorf("checkDaemonExportOK: %w", err) 87 } 88 89 // Initialize Issue Labels if selected 90 if len(opts.IssueLabels) > 0 { 91 if err := repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { 92 return fmt.Errorf("InitializeLabels: %w", err) 93 } 94 } 95 96 if stdout, _, err := git.NewCommand(ctx, "update-server-info"). 97 SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). 98 RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { 99 log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) 100 return fmt.Errorf("CreateRepository(git update-server-info): %w", err) 101 } 102 return nil 103 }); err != nil { 104 return nil, err 105 } 106 107 notify_service.AdoptRepository(ctx, doer, u, repo) 108 109 return repo, nil 110 } 111 112 func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, defaultBranch string) (err error) { 113 isExist, err := util.IsExist(repoPath) 114 if err != nil { 115 log.Error("Unable to check if %s exists. Error: %v", repoPath, err) 116 return err 117 } 118 if !isExist { 119 return fmt.Errorf("adoptRepository: path does not already exist: %s", repoPath) 120 } 121 122 if err := repo_module.CreateDelegateHooks(repoPath); err != nil { 123 return fmt.Errorf("createDelegateHooks: %w", err) 124 } 125 126 repo.IsEmpty = false 127 128 // Don't bother looking this repo in the context it won't be there 129 gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) 130 if err != nil { 131 return fmt.Errorf("openRepository: %w", err) 132 } 133 defer gitRepo.Close() 134 135 if len(defaultBranch) > 0 { 136 repo.DefaultBranch = defaultBranch 137 138 if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { 139 return fmt.Errorf("setDefaultBranch: %w", err) 140 } 141 } else { 142 repo.DefaultBranch, err = gitRepo.GetDefaultBranch() 143 if err != nil { 144 repo.DefaultBranch = setting.Repository.DefaultBranch 145 if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { 146 return fmt.Errorf("setDefaultBranch: %w", err) 147 } 148 } 149 } 150 151 branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ 152 RepoID: repo.ID, 153 ListOptions: db.ListOptions{ 154 ListAll: true, 155 }, 156 IsDeletedBranch: util.OptionalBoolFalse, 157 }) 158 159 found := false 160 hasDefault := false 161 hasMaster := false 162 hasMain := false 163 for _, branch := range branches { 164 if branch == repo.DefaultBranch { 165 found = true 166 break 167 } else if branch == setting.Repository.DefaultBranch { 168 hasDefault = true 169 } else if branch == "master" { 170 hasMaster = true 171 } else if branch == "main" { 172 hasMain = true 173 } 174 } 175 if !found { 176 if hasDefault { 177 repo.DefaultBranch = setting.Repository.DefaultBranch 178 } else if hasMaster { 179 repo.DefaultBranch = "master" 180 } else if hasMain { 181 repo.DefaultBranch = "main" 182 } else if len(branches) > 0 { 183 repo.DefaultBranch = branches[0] 184 } else { 185 repo.IsEmpty = true 186 repo.DefaultBranch = setting.Repository.DefaultBranch 187 } 188 189 if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { 190 return fmt.Errorf("setDefaultBranch: %w", err) 191 } 192 } 193 194 if err = repo_module.UpdateRepository(ctx, repo, false); err != nil { 195 return fmt.Errorf("updateRepository: %w", err) 196 } 197 198 if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { 199 return fmt.Errorf("SyncReleasesWithTags: %w", err) 200 } 201 202 return nil 203 } 204 205 // DeleteUnadoptedRepository deletes unadopted repository files from the filesystem 206 func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, repoName string) error { 207 if err := repo_model.IsUsableRepoName(repoName); err != nil { 208 return err 209 } 210 211 repoPath := repo_model.RepoPath(u.Name, repoName) 212 isExist, err := util.IsExist(repoPath) 213 if err != nil { 214 log.Error("Unable to check if %s exists. Error: %v", repoPath, err) 215 return err 216 } 217 if !isExist { 218 return repo_model.ErrRepoNotExist{ 219 OwnerName: u.Name, 220 Name: repoName, 221 } 222 } 223 224 if exist, err := repo_model.IsRepositoryModelExist(ctx, u, repoName); err != nil { 225 return err 226 } else if exist { 227 return repo_model.ErrRepoAlreadyExist{ 228 Uname: u.Name, 229 Name: repoName, 230 } 231 } 232 233 return util.RemoveAll(repoPath) 234 } 235 236 type unadoptedRepositories struct { 237 repositories []string 238 index int 239 start int 240 end int 241 } 242 243 func (unadopted *unadoptedRepositories) add(repository string) { 244 if unadopted.index >= unadopted.start && unadopted.index < unadopted.end { 245 unadopted.repositories = append(unadopted.repositories, repository) 246 } 247 unadopted.index++ 248 } 249 250 func checkUnadoptedRepositories(ctx context.Context, userName string, repoNamesToCheck []string, unadopted *unadoptedRepositories) error { 251 if len(repoNamesToCheck) == 0 { 252 return nil 253 } 254 ctxUser, err := user_model.GetUserByName(ctx, userName) 255 if err != nil { 256 if user_model.IsErrUserNotExist(err) { 257 log.Debug("Missing user: %s", userName) 258 return nil 259 } 260 return err 261 } 262 repos, _, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{ 263 Actor: ctxUser, 264 Private: true, 265 ListOptions: db.ListOptions{ 266 Page: 1, 267 PageSize: len(repoNamesToCheck), 268 }, LowerNames: repoNamesToCheck, 269 }) 270 if err != nil { 271 return err 272 } 273 if len(repos) == len(repoNamesToCheck) { 274 return nil 275 } 276 repoNames := make(container.Set[string], len(repos)) 277 for _, repo := range repos { 278 repoNames.Add(repo.LowerName) 279 } 280 for _, repoName := range repoNamesToCheck { 281 if !repoNames.Contains(repoName) { 282 unadopted.add(path.Join(userName, repoName)) // These are not used as filepaths - but as reponames - therefore use path.Join not filepath.Join 283 } 284 } 285 return nil 286 } 287 288 // ListUnadoptedRepositories lists all the unadopted repositories that match the provided query 289 func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListOptions) ([]string, int, error) { 290 globUser, _ := glob.Compile("*") 291 globRepo, _ := glob.Compile("*") 292 293 qsplit := strings.SplitN(query, "/", 2) 294 if len(qsplit) > 0 && len(query) > 0 { 295 var err error 296 globUser, err = glob.Compile(qsplit[0]) 297 if err != nil { 298 log.Info("Invalid glob expression '%s' (skipped): %v", qsplit[0], err) 299 } 300 if len(qsplit) > 1 { 301 globRepo, err = glob.Compile(qsplit[1]) 302 if err != nil { 303 log.Info("Invalid glob expression '%s' (skipped): %v", qsplit[1], err) 304 } 305 } 306 } 307 var repoNamesToCheck []string 308 309 start := (opts.Page - 1) * opts.PageSize 310 unadopted := &unadoptedRepositories{ 311 repositories: make([]string, 0, opts.PageSize), 312 start: start, 313 end: start + opts.PageSize, 314 index: 0, 315 } 316 317 var userName string 318 319 // We're going to iterate by pagesize. 320 root := filepath.Clean(setting.RepoRootPath) 321 if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { 322 if err != nil { 323 return err 324 } 325 if !d.IsDir() || path == root { 326 return nil 327 } 328 329 name := d.Name() 330 331 if !strings.ContainsRune(path[len(root)+1:], filepath.Separator) { 332 // Got a new user 333 if err = checkUnadoptedRepositories(ctx, userName, repoNamesToCheck, unadopted); err != nil { 334 return err 335 } 336 repoNamesToCheck = repoNamesToCheck[:0] 337 338 if !globUser.Match(name) { 339 return filepath.SkipDir 340 } 341 342 userName = name 343 return nil 344 } 345 346 if !strings.HasSuffix(name, ".git") { 347 return filepath.SkipDir 348 } 349 name = name[:len(name)-4] 350 if repo_model.IsUsableRepoName(name) != nil || strings.ToLower(name) != name || !globRepo.Match(name) { 351 return filepath.SkipDir 352 } 353 354 repoNamesToCheck = append(repoNamesToCheck, name) 355 if len(repoNamesToCheck) >= setting.Database.IterateBufferSize { 356 if err = checkUnadoptedRepositories(ctx, userName, repoNamesToCheck, unadopted); err != nil { 357 return err 358 } 359 repoNamesToCheck = repoNamesToCheck[:0] 360 361 } 362 return filepath.SkipDir 363 }); err != nil { 364 return nil, 0, err 365 } 366 367 if err := checkUnadoptedRepositories(ctx, userName, repoNamesToCheck, unadopted); err != nil { 368 return nil, 0, err 369 } 370 371 return unadopted.repositories, unadopted.index, nil 372 }