|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- // Copyright 2017 The Gitea Authors. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
-
- package models
-
- import (
- "io/ioutil"
- "os"
- "path"
- "strconv"
- "strings"
-
- "code.gitea.io/git"
- "code.gitea.io/gitea/modules/base"
- "code.gitea.io/gitea/modules/indexer"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
-
- "github.com/Unknwon/com"
- )
-
- // RepoIndexerStatus status of a repo's entry in the repo indexer
- // For now, implicitly refers to default branch
- type RepoIndexerStatus struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"INDEX"`
- CommitSha string `xorm:"VARCHAR(40)"`
- }
-
- func (repo *Repository) getIndexerStatus() error {
- if repo.IndexerStatus != nil {
- return nil
- }
- status := &RepoIndexerStatus{RepoID: repo.ID}
- has, err := x.Get(status)
- if err != nil {
- return err
- } else if !has {
- status.CommitSha = ""
- }
- repo.IndexerStatus = status
- return nil
- }
-
- func (repo *Repository) updateIndexerStatus(sha string) error {
- if err := repo.getIndexerStatus(); err != nil {
- return err
- }
- if len(repo.IndexerStatus.CommitSha) == 0 {
- repo.IndexerStatus.CommitSha = sha
- _, err := x.Insert(repo.IndexerStatus)
- return err
- }
- repo.IndexerStatus.CommitSha = sha
- _, err := x.ID(repo.IndexerStatus.ID).Cols("commit_sha").
- Update(repo.IndexerStatus)
- return err
- }
-
- type repoIndexerOperation struct {
- repo *Repository
- deleted bool
- }
-
- var repoIndexerOperationQueue chan repoIndexerOperation
-
- // InitRepoIndexer initialize the repo indexer
- func InitRepoIndexer() {
- if !setting.Indexer.RepoIndexerEnabled {
- return
- }
- indexer.InitRepoIndexer(populateRepoIndexer)
- repoIndexerOperationQueue = make(chan repoIndexerOperation, setting.Indexer.UpdateQueueLength)
- go processRepoIndexerOperationQueue()
- }
-
- // populateRepoIndexer populate the repo indexer with data
- func populateRepoIndexer() error {
- log.Info("Populating repository indexer (this may take a while)")
- for page := 1; ; page++ {
- repos, _, err := SearchRepositoryByName(&SearchRepoOptions{
- Page: page,
- PageSize: 10,
- OrderBy: SearchOrderByID,
- Private: true,
- })
- if err != nil {
- return err
- } else if len(repos) == 0 {
- return nil
- }
- for _, repo := range repos {
- if err = updateRepoIndexer(repo); err != nil {
- // only log error, since this should not prevent
- // gitea from starting up
- log.Error(4, "updateRepoIndexer: repoID=%d, %v", repo.ID, err)
- }
- }
- }
- }
-
- type updateBatch struct {
- updates []indexer.RepoIndexerUpdate
- }
-
- func updateRepoIndexer(repo *Repository) error {
- changes, err := getRepoChanges(repo)
- if err != nil {
- return err
- } else if changes == nil {
- return nil
- }
-
- batch := indexer.RepoIndexerBatch()
- for _, filename := range changes.UpdatedFiles {
- if err := addUpdate(filename, repo, batch); err != nil {
- return err
- }
- }
- for _, filename := range changes.RemovedFiles {
- if err := addDelete(filename, repo, batch); err != nil {
- return err
- }
- }
- if err = batch.Flush(); err != nil {
- return err
- }
- return updateLastIndexSync(repo)
- }
-
- // repoChanges changes (file additions/updates/removals) to a repo
- type repoChanges struct {
- UpdatedFiles []string
- RemovedFiles []string
- }
-
- // getRepoChanges returns changes to repo since last indexer update
- func getRepoChanges(repo *Repository) (*repoChanges, error) {
- repoWorkingPool.CheckIn(com.ToStr(repo.ID))
- defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
-
- if err := repo.UpdateLocalCopyBranch(""); err != nil {
- return nil, err
- } else if !git.IsBranchExist(repo.LocalCopyPath(), repo.DefaultBranch) {
- // repo does not have any commits yet, so nothing to update
- return nil, nil
- } else if err = repo.UpdateLocalCopyBranch(repo.DefaultBranch); err != nil {
- return nil, err
- } else if err = repo.getIndexerStatus(); err != nil {
- return nil, err
- }
-
- if len(repo.IndexerStatus.CommitSha) == 0 {
- return genesisChanges(repo)
- }
- return nonGenesisChanges(repo)
- }
-
- func addUpdate(filename string, repo *Repository, batch *indexer.Batch) error {
- filepath := path.Join(repo.LocalCopyPath(), filename)
- if stat, err := os.Stat(filepath); err != nil {
- return err
- } else if stat.Size() > setting.Indexer.MaxIndexerFileSize {
- return nil
- }
- fileContents, err := ioutil.ReadFile(filepath)
- if err != nil {
- return err
- } else if !base.IsTextFile(fileContents) {
- return nil
- }
- return batch.Add(indexer.RepoIndexerUpdate{
- Filepath: filename,
- Op: indexer.RepoIndexerOpUpdate,
- Data: &indexer.RepoIndexerData{
- RepoID: repo.ID,
- Content: string(fileContents),
- },
- })
- }
-
- func addDelete(filename string, repo *Repository, batch *indexer.Batch) error {
- return batch.Add(indexer.RepoIndexerUpdate{
- Filepath: filename,
- Op: indexer.RepoIndexerOpDelete,
- Data: &indexer.RepoIndexerData{
- RepoID: repo.ID,
- },
- })
- }
-
- // genesisChanges get changes to add repo to the indexer for the first time
- func genesisChanges(repo *Repository) (*repoChanges, error) {
- var changes repoChanges
- stdout, err := git.NewCommand("ls-files").RunInDir(repo.LocalCopyPath())
- if err != nil {
- return nil, err
- }
- for _, line := range strings.Split(stdout, "\n") {
- filename := strings.TrimSpace(line)
- if len(filename) == 0 {
- continue
- } else if filename[0] == '"' {
- filename, err = strconv.Unquote(filename)
- if err != nil {
- return nil, err
- }
- }
- changes.UpdatedFiles = append(changes.UpdatedFiles, filename)
- }
- return &changes, nil
- }
-
- // nonGenesisChanges get changes since the previous indexer update
- func nonGenesisChanges(repo *Repository) (*repoChanges, error) {
- diffCmd := git.NewCommand("diff", "--name-status",
- repo.IndexerStatus.CommitSha, "HEAD")
- stdout, err := diffCmd.RunInDir(repo.LocalCopyPath())
- if err != nil {
- // previous commit sha may have been removed by a force push, so
- // try rebuilding from scratch
- if err = indexer.DeleteRepoFromIndexer(repo.ID); err != nil {
- return nil, err
- }
- return genesisChanges(repo)
- }
- var changes repoChanges
- for _, line := range strings.Split(stdout, "\n") {
- line = strings.TrimSpace(line)
- if len(line) == 0 {
- continue
- }
- filename := strings.TrimSpace(line[1:])
- if len(filename) == 0 {
- continue
- } else if filename[0] == '"' {
- filename, err = strconv.Unquote(filename)
- if err != nil {
- return nil, err
- }
- }
-
- switch status := line[0]; status {
- case 'M', 'A':
- changes.UpdatedFiles = append(changes.UpdatedFiles, filename)
- case 'D':
- changes.RemovedFiles = append(changes.RemovedFiles, filename)
- default:
- log.Warn("Unrecognized status: %c (line=%s)", status, line)
- }
- }
- return &changes, nil
- }
-
- func updateLastIndexSync(repo *Repository) error {
- stdout, err := git.NewCommand("rev-parse", "HEAD").RunInDir(repo.LocalCopyPath())
- if err != nil {
- return err
- }
- sha := strings.TrimSpace(stdout)
- return repo.updateIndexerStatus(sha)
- }
-
- func processRepoIndexerOperationQueue() {
- for {
- op := <-repoIndexerOperationQueue
- if op.deleted {
- if err := indexer.DeleteRepoFromIndexer(op.repo.ID); err != nil {
- log.Error(4, "DeleteRepoFromIndexer: %v", err)
- }
- } else {
- if err := updateRepoIndexer(op.repo); err != nil {
- log.Error(4, "updateRepoIndexer: %v", err)
- }
- }
- }
- }
-
- // DeleteRepoFromIndexer remove all of a repository's entries from the indexer
- func DeleteRepoFromIndexer(repo *Repository) {
- addOperationToQueue(repoIndexerOperation{repo: repo, deleted: true})
- }
-
- // UpdateRepoIndexer update a repository's entries in the indexer
- func UpdateRepoIndexer(repo *Repository) {
- addOperationToQueue(repoIndexerOperation{repo: repo, deleted: false})
- }
-
- func addOperationToQueue(op repoIndexerOperation) {
- if !setting.Indexer.RepoIndexerEnabled {
- return
- }
- select {
- case repoIndexerOperationQueue <- op:
- break
- default:
- go func() {
- repoIndexerOperationQueue <- op
- }()
- }
- }
|