nil { return fmt.Errorf("Commit: %v", err) } if org.IsOrganization() { if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{ Action: api.HookRepoDeleted, Repository: repo.APIFormat(AccessModeOwner), Organization: org.APIFormat(), Sender: doer.APIFormat(), }); err != nil { return err } go HookQueue.Add(repo.ID) } DeleteRepoFromIndexer(repo) return nil } // GetRepositoryByRef returns a Repository specified by a GFM reference. // See https://help.github.com/articles/writing-on-github#references for more information on the syntax. func GetRepositoryByRef(ref string) (*Repository, error) { n := strings.IndexByte(ref, byte('/')) if n < 2 { return nil, ErrInvalidReference } userName, repoName := ref[:n], ref[n+1:] user, err := GetUserByName(userName) if err != nil { return nil, err } return GetRepositoryByName(user.ID, repoName) } // GetRepositoryByName returns the repository by given name under user if exists. func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { repo := &Repository{ OwnerID: ownerID, LowerName: strings.ToLower(name), } has, err := x.Get(repo) if err != nil { return nil, err } else if !has { return nil, ErrRepoNotExist{0, ownerID, name} } return repo, err } func getRepositoryByID(e Engine, id int64) (*Repository, error) { repo := new(Repository) has, err := e.ID(id).Get(repo) if err != nil { return nil, err } else if !has { return nil, ErrRepoNotExist{id, 0, ""} } return repo, nil } // GetRepositoryByID returns the repository by given id if exists. func GetRepositoryByID(id int64) (*Repository, error) { return getRepositoryByID(x, id) } // GetUserRepositories returns a list of repositories of given user. func GetUserRepositories(userID int64, private bool, page, pageSize int, orderBy string) ([]*Repository, error) { if len(orderBy) == 0 { orderBy = "updated_unix DESC" } sess := x. Where("owner_id = ?", userID). OrderBy(orderBy) if !private { sess.And("is_private=?", false) } if page <= 0 { page = 1 } sess.Limit(pageSize, (page-1)*pageSize) repos := make([]*Repository, 0, pageSize) return repos, sess.Find(&repos) } // GetUserMirrorRepositories returns a list of mirror repositories of given user. func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { repos := make([]*Repository, 0, 10) return repos, x. Where("owner_id = ?", userID). And("is_mirror = ?", true). Find(&repos) } func getRepositoryCount(e Engine, u *User) (int64, error) { return e.Count(&Repository{OwnerID: u.ID}) } func getPublicRepositoryCount(e Engine, u *User) (int64, error) { return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) } func getPrivateRepositoryCount(e Engine, u *User) (int64, error) { return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) } // GetRepositoryCount returns the total number of repositories of user. func GetRepositoryCount(u *User) (int64, error) { return getRepositoryCount(x, u) } // GetPublicRepositoryCount returns the total number of public repositories of user. func GetPublicRepositoryCount(u *User) (int64, error) { return getPublicRepositoryCount(x, u) } // GetPrivateRepositoryCount returns the total number of private repositories of user. func GetPrivateRepositoryCount(u *User) (int64, error) { return getPrivateRepositoryCount(x, u) } // DeleteRepositoryArchives deletes all repositories' archives. func DeleteRepositoryArchives() error { return x. Where("id > 0"). Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")) }) } // DeleteOldRepositoryArchives deletes old repository archives. func DeleteOldRepositoryArchives() { if !taskStatusTable.StartIfNotRunning(archiveCleanup) { return } defer taskStatusTable.Stop(archiveCleanup) log.Trace("Doing: ArchiveCleanup") if err := x.Where("id > 0").Iterate(new(Repository), deleteOldRepositoryArchives); err != nil { log.Error(4, "ArchiveClean: %v", err) } } func deleteOldRepositoryArchives(idx int, bean interface{}) error { repo := bean.(*Repository) basePath := filepath.Join(repo.RepoPath(), "archives") for _, ty := range []string{"zip", "targz"} { path := filepath.Join(basePath, ty) file, err := os.Open(path) if err != nil { if !os.IsNotExist(err) { log.Warn("Unable to open directory %s: %v", path, err) return err } // If the directory doesn't exist, that's okay. continue } files, err := file.Readdir(0) file.Close() if err != nil { log.Warn("Unable to read directory %s: %v", path, err) return err } minimumOldestTime := time.Now().Add(-setting.Cron.ArchiveCleanup.OlderThan) for _, info := range files { if info.ModTime().Before(minimumOldestTime) && !info.IsDir() { toDelete := filepath.Join(path, info.Name()) // This is a best-effort purge, so we do not check error codes to confirm removal. if err = os.Remove(toDelete); err != nil { log.Trace("Unable to delete %s, but proceeding: %v", toDelete, err) } } } } return nil } func gatherMissingRepoRecords() ([]*Repository, error) { repos := make([]*Repository, 0, 10) if err := x. Where("id > 0"). Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) if !com.IsDir(repo.RepoPath()) { repos = append(repos, repo) } return nil }); err != nil { if err2 := CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) } } return repos, nil } // DeleteMissingRepositories deletes all repository records that lost Git files. func DeleteMissingRepositories(doer *User) error { repos, err := gatherMissingRepoRecords() if err != nil { return fmt.Errorf("gatherMissingRepoRecords: %v", err) } if len(repos) == 0 { return nil } for _, repo := range repos { log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) if err := DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { return fmt.Errorf("CreateRepositoryNotice: %v", err) } } } return nil } // ReinitMissingRepositories reinitializes all repository records that lost Git files. func ReinitMissingRepositories() error { repos, err := gatherMissingRepoRecords() if err != nil { return fmt.Errorf("gatherMissingRepoRecords: %v", err) } if len(repos) == 0 { return nil } for _, repo := range repos { log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) if err := git.InitRepository(repo.RepoPath(), true); err != nil { if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { return fmt.Errorf("CreateRepositoryNotice: %v", err) } } } return nil } // SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks // to make sure the binary and custom conf path are up-to-date. func SyncRepositoryHooks() error { return x.Where("id > 0").Iterate(new(Repository), func(idx int, bean interface{}) error { if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil { return fmt.Errorf("SyncRepositoryHook: %v", err) } if bean.(*Repository).HasWiki() { if err := createDelegateHooks(bean.(*Repository).WikiPath()); err != nil { return fmt.Errorf("SyncRepositoryHook: %v", err) } } return nil }) } // Prevent duplicate running tasks. var taskStatusTable = sync.NewStatusTable() const ( mirrorUpdate = "mirror_update" gitFsck = "git_fsck" checkRepos = "check_repos" archiveCleanup = "archive_cleanup" ) // GitFsck calls 'git fsck' to check repository health. func GitFsck() { if !taskStatusTable.StartIfNotRunning(gitFsck) { return } defer taskStatusTable.Stop(gitFsck) log.Trace("Doing: GitFsck") if err := x. Where("id>0").BufferSize(setting.IterateBufferSize). Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) repoPath := repo.RepoPath() if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) log.Warn(desc) if err = CreateRepositoryNotice(desc); err != nil { log.Error(4, "CreateRepositoryNotice: %v", err) } } return nil }); err != nil { log.Error(4, "GitFsck: %v", err) } } // GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository func GitGcRepos() error { args := append([]string{"gc"}, setting.Git.GCArgs...) return x. Where("id > 0").BufferSize(setting.IterateBufferSize). Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) if err := repo.GetOwner(); err != nil { return err } _, stderr, err := process.GetManager().ExecDir( time.Duration(setting.Git.Timeout.GC)*time.Second, RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection", "git", args...) if err != nil { return fmt.Errorf("%v: %v", err, stderr) } return nil }) } type repoChecker struct { querySQL, correctSQL string desc string } func repoStatsCheck(checker *repoChecker) { results, err := x.Query(checker.querySQL) if err != nil { log.Error(4, "Select %s: %v", checker.desc, err) return } for _, result := range results { id := com.StrTo(result["id"]).MustInt64() log.Trace("Updating %s: %d", checker.desc, id) _, err = x.Exec(checker.correctSQL, id, id) if err != nil { log.Error(4, "Update %s[%d]: %v", checker.desc, id, err) } } } // CheckRepoStats checks the repository stats func CheckRepoStats() { if !taskStatusTable.StartIfNotRunning(checkRepos) { return } defer taskStatusTable.Stop(checkRepos) log.Trace("Doing: CheckRepoStats") checkers := []*repoChecker{ // Repository.NumWatches { "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)", "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?", "repository count 'num_watches'", }, // Repository.NumStars { "SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)", "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", "repository count 'num_stars'", }, // Label.NumIssues { "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)", "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", "label count 'num_issues'", }, // User.NumRepos { "SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)", "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", "user count 'num_repos'", }, // Issue.NumComments { "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)", "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", "issue count 'num_comments'", }, } for i := range checkers { repoStatsCheck(checkers[i]) } // ***** START: Repository.NumClosedIssues ***** desc := "repository count 'num_closed_issues'" results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) if err != nil { log.Error(4, "Select %s: %v", desc, err) } else { for _, result := range results { id := com.StrTo(result["id"]).MustInt64() log.Trace("Updating %s: %d", desc, id) _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) if err != nil { log.Error(4, "Update %s[%d]: %v", desc, id, err) } } } // ***** END: Repository.NumClosedIssues ***** // FIXME: use checker when stop supporting old fork repo format. // ***** START: Repository.NumForks ***** results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") if err != nil { log.Error(4, "Select repository count 'num_forks': %v", err) } else { for _, result := range results { id := com.StrTo(result["id"]).MustInt64() log.Trace("Updating repository count 'num_forks': %d", id) repo, err := GetRepositoryByID(id) if err != nil { log.Error(4, "GetRepositoryByID[%d]: %v", id, err) continue } rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) if err != nil { log.Error(4, "Select count of forks[%d]: %v", repo.ID, err) continue } repo.NumForks = int(parseCountResult(rawResult)) if err = UpdateRepository(repo, false); err != nil { log.Error(4, "UpdateRepository[%d]: %v", id, err) continue } } } // ***** END: Repository.NumForks ***** } // ___________ __ // \_ _____/__________| | __ // | __)/ _ \_ __ \ |/ / // | \( <_> ) | \/ < // \___ / \____/|__| |__|_ \ // \/ \/ // HasForkedRepo checks if given user has already forked a repository with given ID. func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { repo := new(Repository) has, _ := x. Where("owner_id=? AND fork_id=?", ownerID, repoID). Get(repo) return repo, has } // ForkRepository forks a repository func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { forkedRepo, err := oldRepo.GetUserFork(u.ID) if err != nil { return nil, err } if forkedRepo != nil { return nil, ErrRepoAlreadyExist{ Uname: u.Name, Name: forkedRepo.Name, } } repo := &Repository{ OwnerID: u.ID, Owner: u, Name: name, LowerName: strings.ToLower(name), Description: desc, DefaultBranch: oldRepo.DefaultBranch, IsPrivate: oldRepo.IsPrivate, IsFork: true, ForkID: oldRepo.ID, } sess := x.NewSession() defer sess.Close() if err = sess.Begin(); err != nil { return nil, err } if err = createRepository(sess, doer, u, repo); err != nil { return nil, err } if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { return nil, err } repoPath := RepoPath(u.Name, repo.Name) _, stderr, err := process.GetManager().ExecTimeout(10*time.Minute, fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name), "git", "clone", "--bare", oldRepo.RepoPath(), repoPath) if err != nil { return nil, fmt.Errorf("git clone: %v", stderr) } _, stderr, err = process.GetManager().ExecDir(-1, repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath), "git", "update-server-info") if err != nil { return nil, fmt.Errorf("git update-server-info: %v", stderr) } if err = createDelegateHooks(repoPath); err != nil { return nil, fmt.Errorf("createDelegateHooks: %v", err) } //Commit repo to get Fork ID err = sess.Commit() if err != nil { return nil, err } if err = repo.UpdateSize(); err != nil { log.Error(4, "Failed to update size for repository: %v", err) } // Copy LFS meta objects in new session sess2 := x.NewSession() defer sess2.Close() if err = sess2.Begin(); err != nil { return nil, err } var lfsObjects []*LFSMetaObject if err = sess2.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { return nil, err } for _, v := range lfsObjects { v.ID = 0 v.RepositoryID = repo.ID if _, err = sess2.Insert(v); err != nil { return nil, err } } return repo, sess2.Commit() } // GetForks returns all the forks of the repository func (repo *Repository) GetForks() ([]*Repository, error) { forks := make([]*Repository, 0, repo.NumForks) return forks, x.Find(&forks, &Repository{ForkID: repo.ID}) } // GetUserFork return user forked repository from this repository, if not forked return nil func (repo *Repository) GetUserFork(userID int64) (*Repository, error) { var forkedRepo Repository has, err := x.Where("fork_id = ?", repo.ID).And("owner_id = ?", userID).Get(&forkedRepo) if err != nil { return nil, err } if !has { return nil, nil } return &forkedRepo, nil }