123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- // Copyright 2019 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 code
-
- import (
- "strconv"
- "strings"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
- )
-
- type fileUpdate struct {
- Filename string
- BlobSha string
- }
-
- // repoChanges changes (file additions/updates/removals) to a repo
- type repoChanges struct {
- Updates []fileUpdate
- RemovedFilenames []string
- }
-
- func getDefaultBranchSha(repo *models.Repository) (string, error) {
- stdout, err := git.NewCommand("show-ref", "-s", git.BranchPrefix+repo.DefaultBranch).RunInDir(repo.RepoPath())
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(stdout), nil
- }
-
- // getRepoChanges returns changes to repo since last indexer update
- func getRepoChanges(repo *models.Repository, revision string) (*repoChanges, error) {
- if err := repo.GetIndexerStatus(); err != nil {
- return nil, err
- }
-
- if len(repo.IndexerStatus.CommitSha) == 0 {
- return genesisChanges(repo, revision)
- }
- return nonGenesisChanges(repo, revision)
- }
-
- func isIndexable(entry *git.TreeEntry) bool {
- if !entry.IsRegular() && !entry.IsExecutable() {
- return false
- }
- name := strings.ToLower(entry.Name())
- for _, g := range setting.Indexer.ExcludePatterns {
- if g.Match(name) {
- return false
- }
- }
- for _, g := range setting.Indexer.IncludePatterns {
- if g.Match(name) {
- return true
- }
- }
- return len(setting.Indexer.IncludePatterns) == 0
- }
-
- // parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command
- func parseGitLsTreeOutput(stdout []byte) ([]fileUpdate, error) {
- entries, err := git.ParseTreeEntries(stdout)
- if err != nil {
- return nil, err
- }
- var idxCount = 0
- updates := make([]fileUpdate, len(entries))
- for _, entry := range entries {
- if isIndexable(entry) {
- updates[idxCount] = fileUpdate{
- Filename: entry.Name(),
- BlobSha: entry.ID.String(),
- }
- idxCount++
- }
- }
- return updates[:idxCount], nil
- }
-
- // genesisChanges get changes to add repo to the indexer for the first time
- func genesisChanges(repo *models.Repository, revision string) (*repoChanges, error) {
- var changes repoChanges
- stdout, err := git.NewCommand("ls-tree", "--full-tree", "-r", revision).
- RunInDirBytes(repo.RepoPath())
- if err != nil {
- return nil, err
- }
- changes.Updates, err = parseGitLsTreeOutput(stdout)
- return &changes, err
- }
-
- // nonGenesisChanges get changes since the previous indexer update
- func nonGenesisChanges(repo *models.Repository, revision string) (*repoChanges, error) {
- diffCmd := git.NewCommand("diff", "--name-status",
- repo.IndexerStatus.CommitSha, revision)
- stdout, err := diffCmd.RunInDir(repo.RepoPath())
- if err != nil {
- // previous commit sha may have been removed by a force push, so
- // try rebuilding from scratch
- log.Warn("git diff: %v", err)
- if err = indexer.Delete(repo.ID); err != nil {
- return nil, err
- }
- return genesisChanges(repo, revision)
- }
- var changes repoChanges
- updatedFilenames := make([]string, 0, 10)
- for _, line := range strings.Split(stdout, "\n") {
- line = strings.TrimSpace(line)
- if len(line) == 0 {
- continue
- }
- fields := strings.Split(line, "\t")
- if len(fields) < 2 {
- log.Warn("Unparseable output for diff --name-status: `%s`)", line)
- continue
- }
- filename := fields[1]
- if len(filename) == 0 {
- continue
- } else if filename[0] == '"' {
- filename, err = strconv.Unquote(filename)
- if err != nil {
- return nil, err
- }
- }
-
- switch status := fields[0][0]; status {
- case 'M', 'A':
- updatedFilenames = append(updatedFilenames, filename)
- case 'D':
- changes.RemovedFilenames = append(changes.RemovedFilenames, filename)
- case 'R', 'C':
- if len(fields) < 3 {
- log.Warn("Unparseable output for diff --name-status: `%s`)", line)
- continue
- }
- dest := fields[2]
- if len(dest) == 0 {
- log.Warn("Unparseable output for diff --name-status: `%s`)", line)
- continue
- }
- if dest[0] == '"' {
- dest, err = strconv.Unquote(dest)
- if err != nil {
- return nil, err
- }
- }
- if status == 'R' {
- changes.RemovedFilenames = append(changes.RemovedFilenames, filename)
- }
- updatedFilenames = append(updatedFilenames, dest)
- default:
- log.Warn("Unrecognized status: %c (line=%s)", status, line)
- }
- }
-
- cmd := git.NewCommand("ls-tree", "--full-tree", revision, "--")
- cmd.AddArguments(updatedFilenames...)
- lsTreeStdout, err := cmd.RunInDirBytes(repo.RepoPath())
- if err != nil {
- return nil, err
- }
- changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout)
- return &changes, err
- }
|