mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Use native git variants by default with go-git variants as build tag (#13673)
* Move last commit cache back into modules/git Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from the interface for last commit cache Signed-off-by: Andrew Thornton <art27@cantab.net> * move cacheref to last_commit_cache Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove go-git from routers/private/hook Signed-off-by: Andrew Thornton <art27@cantab.net> * Move FindLFSFiles to pipeline Signed-off-by: Andrew Thornton <art27@cantab.net> * Make no-go-git variants Signed-off-by: Andrew Thornton <art27@cantab.net> * Submodule RefID Signed-off-by: Andrew Thornton <art27@cantab.net> * fix issue with GetCommitsInfo Signed-off-by: Andrew Thornton <art27@cantab.net> * fix GetLastCommitForPaths Signed-off-by: Andrew Thornton <art27@cantab.net> * Improve efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * More efficiency Signed-off-by: Andrew Thornton <art27@cantab.net> * even faster Signed-off-by: Andrew Thornton <art27@cantab.net> * Reduce duplication * As per @lunny Signed-off-by: Andrew Thornton <art27@cantab.net> * attempt to fix drone Signed-off-by: Andrew Thornton <art27@cantab.net> * fix test-tags Signed-off-by: Andrew Thornton <art27@cantab.net> * default to use no-go-git variants and add gogit build tag Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @6543 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		
							
								
								
									
										29
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -33,6 +33,16 @@ steps:
 | 
				
			|||||||
      GOSUMDB: sum.golang.org
 | 
					      GOSUMDB: sum.golang.org
 | 
				
			||||||
      TAGS: bindata sqlite sqlite_unlock_notify
 | 
					      TAGS: bindata sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: lint-backend-gogit
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    image: golang:1.15
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make lint-backend
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
 | 
				
			||||||
 | 
					      GOSUMDB: sum.golang.org
 | 
				
			||||||
 | 
					      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: checks-frontend
 | 
					  - name: checks-frontend
 | 
				
			||||||
    image: node:14
 | 
					    image: node:14
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
@@ -69,7 +79,7 @@ steps:
 | 
				
			|||||||
      GOPROXY: off
 | 
					      GOPROXY: off
 | 
				
			||||||
      GOOS: linux
 | 
					      GOOS: linux
 | 
				
			||||||
      GOARCH: arm64
 | 
					      GOARCH: arm64
 | 
				
			||||||
      TAGS: bindata
 | 
					      TAGS: bindata gogit
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
      - make backend # test cross compile
 | 
					      - make backend # test cross compile
 | 
				
			||||||
      - rm ./gitea # clean
 | 
					      - rm ./gitea # clean
 | 
				
			||||||
@@ -173,6 +183,17 @@ steps:
 | 
				
			|||||||
      GITHUB_READ_TOKEN:
 | 
					      GITHUB_READ_TOKEN:
 | 
				
			||||||
        from_secret: github_read_token
 | 
					        from_secret: github_read_token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: unit-test-gogit
 | 
				
			||||||
 | 
					    pull: always
 | 
				
			||||||
 | 
					    image: golang:1.15
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make unit-test-coverage test-check
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      GOPROXY: off
 | 
				
			||||||
 | 
					      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					      GITHUB_READ_TOKEN:
 | 
				
			||||||
 | 
					        from_secret: github_read_token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: test-mysql
 | 
					  - name: test-mysql
 | 
				
			||||||
    image: golang:1.15
 | 
					    image: golang:1.15
 | 
				
			||||||
    commands:
 | 
					    commands:
 | 
				
			||||||
@@ -305,7 +326,8 @@ steps:
 | 
				
			|||||||
      - timeout -s ABRT 40m make test-sqlite-migration test-sqlite
 | 
					      - timeout -s ABRT 40m make test-sqlite-migration test-sqlite
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GOPROXY: off
 | 
					      GOPROXY: off
 | 
				
			||||||
      TAGS: bindata
 | 
					      TAGS: bindata gogit sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					      TEST_TAGS: gogit sqlite sqlite_unlock_notify
 | 
				
			||||||
      USE_REPO_TEST_DIR: 1
 | 
					      USE_REPO_TEST_DIR: 1
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - build
 | 
					      - build
 | 
				
			||||||
@@ -318,7 +340,8 @@ steps:
 | 
				
			|||||||
      - timeout -s ABRT 40m make test-pgsql-migration test-pgsql
 | 
					      - timeout -s ABRT 40m make test-pgsql-migration test-pgsql
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GOPROXY: off
 | 
					      GOPROXY: off
 | 
				
			||||||
      TAGS: bindata
 | 
					      TAGS: bindata gogit
 | 
				
			||||||
 | 
					      TEST_TAGS: gogit
 | 
				
			||||||
      TEST_LDAP: 1
 | 
					      TEST_LDAP: 1
 | 
				
			||||||
      USE_REPO_TEST_DIR: 1
 | 
					      USE_REPO_TEST_DIR: 1
 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Makefile
									
									
									
									
									
								
							@@ -110,7 +110,10 @@ TAGS ?=
 | 
				
			|||||||
TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS))
 | 
					TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS))
 | 
				
			||||||
TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
 | 
					TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST_TAGS ?= sqlite sqlite_unlock_notify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GO_DIRS := cmd integrations models modules routers build services vendor tools
 | 
					GO_DIRS := cmd integrations models modules routers build services vendor tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GO_SOURCES := $(wildcard *.go)
 | 
					GO_SOURCES := $(wildcard *.go)
 | 
				
			||||||
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" -not -path modules/options/bindata.go -not -path modules/public/bindata.go -not -path modules/templates/bindata.go)
 | 
					GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" -not -path modules/options/bindata.go -not -path modules/public/bindata.go -not -path modules/templates/bindata.go)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -339,8 +342,8 @@ watch-backend: go-check
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.PHONY: test
 | 
					.PHONY: test
 | 
				
			||||||
test:
 | 
					test:
 | 
				
			||||||
	@echo "Running go test..."
 | 
						@echo "Running go test with -tags '$(TEST_TAGS)'..."
 | 
				
			||||||
	@$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='sqlite sqlite_unlock_notify' $(GO_PACKAGES)
 | 
						@$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='$(TEST_TAGS)' $(GO_PACKAGES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: test-check
 | 
					.PHONY: test-check
 | 
				
			||||||
test-check:
 | 
					test-check:
 | 
				
			||||||
@@ -356,8 +359,8 @@ test-check:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.PHONY: test\#%
 | 
					.PHONY: test\#%
 | 
				
			||||||
test\#%:
 | 
					test\#%:
 | 
				
			||||||
	@echo "Running go test..."
 | 
						@echo "Running go test with -tags '$(TEST_TAGS)'..."
 | 
				
			||||||
	@$(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' -run $(subst .,/,$*) $(GO_PACKAGES)
 | 
						@$(GO) test -mod=vendor -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_PACKAGES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: coverage
 | 
					.PHONY: coverage
 | 
				
			||||||
coverage:
 | 
					coverage:
 | 
				
			||||||
@@ -365,8 +368,8 @@ coverage:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.PHONY: unit-test-coverage
 | 
					.PHONY: unit-test-coverage
 | 
				
			||||||
unit-test-coverage:
 | 
					unit-test-coverage:
 | 
				
			||||||
	@echo "Running unit-test-coverage..."
 | 
						@echo "Running unit-test-coverage -tags '$(TEST_TAGS)'..."
 | 
				
			||||||
	@$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(GO_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
 | 
						@$(GO) test $(GOTESTFLAGS) -mod=vendor -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: vendor
 | 
					.PHONY: vendor
 | 
				
			||||||
vendor:
 | 
					vendor:
 | 
				
			||||||
@@ -511,7 +514,7 @@ integrations.mssql.test: git-check $(GO_SOURCES)
 | 
				
			|||||||
	$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mssql.test
 | 
						$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mssql.test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
integrations.sqlite.test: git-check $(GO_SOURCES)
 | 
					integrations.sqlite.test: git-check $(GO_SOURCES)
 | 
				
			||||||
	$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags 'sqlite sqlite_unlock_notify'
 | 
						$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags '$(TEST_TAGS)'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
integrations.cover.test: git-check $(GO_SOURCES)
 | 
					integrations.cover.test: git-check $(GO_SOURCES)
 | 
				
			||||||
	$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.test
 | 
						$(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.test
 | 
				
			||||||
@@ -534,7 +537,7 @@ migrations.mssql.test: $(GO_SOURCES)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.PHONY: migrations.sqlite.test
 | 
					.PHONY: migrations.sqlite.test
 | 
				
			||||||
migrations.sqlite.test: $(GO_SOURCES)
 | 
					migrations.sqlite.test: $(GO_SOURCES)
 | 
				
			||||||
	$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags 'sqlite sqlite_unlock_notify'
 | 
						$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: check
 | 
					.PHONY: check
 | 
				
			||||||
check: test
 | 
					check: test
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,6 +101,7 @@ Depending on requirements, the following build tags can be included.
 | 
				
			|||||||
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules). Can
 | 
					- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules). Can
 | 
				
			||||||
  be used to authenticate local users or extend authentication to methods
 | 
					  be used to authenticate local users or extend authentication to methods
 | 
				
			||||||
  available to PAM.
 | 
					  available to PAM.
 | 
				
			||||||
 | 
					* `gogit`: (EXPERIMENTAL) Use go-git variants of git commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Bundling assets into the binary using the `bindata` build tag is recommended for
 | 
					Bundling assets into the binary using the `bindata` build tag is recommended for
 | 
				
			||||||
production deployments. It is possible to serve the static assets directly via a reverse proxy,
 | 
					production deployments. It is possible to serve the static assets directly via a reverse proxy,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								modules/cache/cache.go
									
									
									
									
										vendored
									
									
								
							@@ -27,6 +27,24 @@ func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Cache is the interface that operates the cache data.
 | 
				
			||||||
 | 
					type Cache interface {
 | 
				
			||||||
 | 
						// Put puts value into cache with key and expire time.
 | 
				
			||||||
 | 
						Put(key string, val interface{}, timeout int64) error
 | 
				
			||||||
 | 
						// Get gets cached value by given key.
 | 
				
			||||||
 | 
						Get(key string) interface{}
 | 
				
			||||||
 | 
						// Delete deletes cached value by given key.
 | 
				
			||||||
 | 
						Delete(key string) error
 | 
				
			||||||
 | 
						// Incr increases cached int-type value by given key as a counter.
 | 
				
			||||||
 | 
						Incr(key string) error
 | 
				
			||||||
 | 
						// Decr decreases cached int-type value by given key as a counter.
 | 
				
			||||||
 | 
						Decr(key string) error
 | 
				
			||||||
 | 
						// IsExist returns true if cached value exists.
 | 
				
			||||||
 | 
						IsExist(key string) bool
 | 
				
			||||||
 | 
						// Flush deletes all cached data.
 | 
				
			||||||
 | 
						Flush() error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewContext start cache service
 | 
					// NewContext start cache service
 | 
				
			||||||
func NewContext() error {
 | 
					func NewContext() error {
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
@@ -40,6 +58,11 @@ func NewContext() error {
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCache returns the currently configured cache
 | 
				
			||||||
 | 
					func GetCache() Cache {
 | 
				
			||||||
 | 
						return conn
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetString returns the key value from cache with callback when no key exists in cache
 | 
					// GetString returns the key value from cache with callback when no key exists in cache
 | 
				
			||||||
func GetString(key string, getFunc func() (string, error)) (string, error) {
 | 
					func GetString(key string, getFunc func() (string, error)) (string, error) {
 | 
				
			||||||
	if conn == nil || setting.CacheService.TTL == 0 {
 | 
						if conn == nil || setting.CacheService.TTL == 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										70
									
								
								modules/cache/last_commit.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								modules/cache/last_commit.go
									
									
									
									
										vendored
									
									
								
							@@ -1,70 +0,0 @@
 | 
				
			|||||||
// Copyright 2020 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 cache
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"crypto/sha256"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mc "gitea.com/macaron/cache"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// LastCommitCache represents a cache to store last commit
 | 
					 | 
				
			||||||
type LastCommitCache struct {
 | 
					 | 
				
			||||||
	repoPath    string
 | 
					 | 
				
			||||||
	ttl         int64
 | 
					 | 
				
			||||||
	repo        *git.Repository
 | 
					 | 
				
			||||||
	commitCache map[string]*object.Commit
 | 
					 | 
				
			||||||
	mc.Cache
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewLastCommitCache creates a new last commit cache for repo
 | 
					 | 
				
			||||||
func NewLastCommitCache(repoPath string, gitRepo *git.Repository, ttl int64) *LastCommitCache {
 | 
					 | 
				
			||||||
	return &LastCommitCache{
 | 
					 | 
				
			||||||
		repoPath:    repoPath,
 | 
					 | 
				
			||||||
		repo:        gitRepo,
 | 
					 | 
				
			||||||
		commitCache: make(map[string]*object.Commit),
 | 
					 | 
				
			||||||
		ttl:         ttl,
 | 
					 | 
				
			||||||
		Cache:       conn,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c LastCommitCache) getCacheKey(repoPath, ref, entryPath string) string {
 | 
					 | 
				
			||||||
	hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, ref, entryPath)))
 | 
					 | 
				
			||||||
	return fmt.Sprintf("last_commit:%x", hashBytes)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Get get the last commit information by commit id and entry path
 | 
					 | 
				
			||||||
func (c LastCommitCache) Get(ref, entryPath string) (*object.Commit, error) {
 | 
					 | 
				
			||||||
	v := c.Cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
 | 
					 | 
				
			||||||
	if vs, ok := v.(string); ok {
 | 
					 | 
				
			||||||
		log.Trace("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
 | 
					 | 
				
			||||||
		if commit, ok := c.commitCache[vs]; ok {
 | 
					 | 
				
			||||||
			log.Trace("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
 | 
					 | 
				
			||||||
			return commit, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		id, err := c.repo.ConvertToSHA1(vs)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		commit, err := c.repo.GoGitRepo().CommitObject(id)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		c.commitCache[vs] = commit
 | 
					 | 
				
			||||||
		return commit, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Put put the last commit id with commit and entry path
 | 
					 | 
				
			||||||
func (c LastCommitCache) Put(ref, entryPath, commitID string) error {
 | 
					 | 
				
			||||||
	log.Trace("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
 | 
					 | 
				
			||||||
	return c.Cache.Put(c.getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -13,7 +13,6 @@ import (
 | 
				
			|||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,7 +20,7 @@ func TestToCommitMeta(t *testing.T) {
 | 
				
			|||||||
	assert.NoError(t, models.PrepareTestDatabase())
 | 
						assert.NoError(t, models.PrepareTestDatabase())
 | 
				
			||||||
	headRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
						headRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
				
			||||||
	sha1, _ := git.NewIDFromString("0000000000000000000000000000000000000000")
 | 
						sha1, _ := git.NewIDFromString("0000000000000000000000000000000000000000")
 | 
				
			||||||
	signature := &object.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)}
 | 
						signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)}
 | 
				
			||||||
	tag := &git.Tag{
 | 
						tag := &git.Tag{
 | 
				
			||||||
		Name:    "Test Tag",
 | 
							Name:    "Test Tag",
 | 
				
			||||||
		ID:      sha1,
 | 
							ID:      sha1,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										243
									
								
								modules/git/batch_reader_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								modules/git/batch_reader_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,243 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadBatchLine reads the header line from cat-file --batch
 | 
				
			||||||
 | 
					// We expect:
 | 
				
			||||||
 | 
					// <sha> SP <type> SP <size> LF
 | 
				
			||||||
 | 
					func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err error) {
 | 
				
			||||||
 | 
						sha, err = rd.ReadBytes(' ')
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sha = sha[:len(sha)-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						typ, err = rd.ReadString(' ')
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						typ = typ[:len(typ)-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var sizeStr string
 | 
				
			||||||
 | 
						sizeStr, err = rd.ReadString('\n')
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size, err = strconv.ParseInt(sizeStr[:len(sizeStr)-1], 10, 64)
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadTagObjectID reads a tag object ID hash from a cat-file --batch stream, throwing away the rest of the stream.
 | 
				
			||||||
 | 
					func ReadTagObjectID(rd *bufio.Reader, size int64) (string, error) {
 | 
				
			||||||
 | 
						id := ""
 | 
				
			||||||
 | 
						var n int64
 | 
				
			||||||
 | 
					headerLoop:
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							line, err := rd.ReadBytes('\n')
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							n += int64(len(line))
 | 
				
			||||||
 | 
							idx := bytes.Index(line, []byte{' '})
 | 
				
			||||||
 | 
							if idx < 0 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if string(line[:idx]) == "object" {
 | 
				
			||||||
 | 
								id = string(line[idx+1 : len(line)-1])
 | 
				
			||||||
 | 
								break headerLoop
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Discard the rest of the tag
 | 
				
			||||||
 | 
						discard := size - n
 | 
				
			||||||
 | 
						for discard > math.MaxInt32 {
 | 
				
			||||||
 | 
							_, err := rd.Discard(math.MaxInt32)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return id, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							discard -= math.MaxInt32
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err := rd.Discard(int(discard))
 | 
				
			||||||
 | 
						return id, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadTreeID reads a tree ID from a cat-file --batch stream, throwing away the rest of the stream.
 | 
				
			||||||
 | 
					func ReadTreeID(rd *bufio.Reader, size int64) (string, error) {
 | 
				
			||||||
 | 
						id := ""
 | 
				
			||||||
 | 
						var n int64
 | 
				
			||||||
 | 
					headerLoop:
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							line, err := rd.ReadBytes('\n')
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							n += int64(len(line))
 | 
				
			||||||
 | 
							idx := bytes.Index(line, []byte{' '})
 | 
				
			||||||
 | 
							if idx < 0 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if string(line[:idx]) == "tree" {
 | 
				
			||||||
 | 
								id = string(line[idx+1 : len(line)-1])
 | 
				
			||||||
 | 
								break headerLoop
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Discard the rest of the commit
 | 
				
			||||||
 | 
						discard := size - n
 | 
				
			||||||
 | 
						for discard > math.MaxInt32 {
 | 
				
			||||||
 | 
							_, err := rd.Discard(math.MaxInt32)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return id, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							discard -= math.MaxInt32
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err := rd.Discard(int(discard))
 | 
				
			||||||
 | 
						return id, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// git tree files are a list:
 | 
				
			||||||
 | 
					// <mode-in-ascii> SP <fname> NUL <20-byte SHA>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Unfortunately this 20-byte notation is somewhat in conflict to all other git tools
 | 
				
			||||||
 | 
					// Therefore we need some method to convert these 20-byte SHAs to a 40-byte SHA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// constant hextable to help quickly convert between 20byte and 40byte hashes
 | 
				
			||||||
 | 
					const hextable = "0123456789abcdef"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// to40ByteSHA converts a 20-byte SHA in a 40-byte slice into a 40-byte sha in place
 | 
				
			||||||
 | 
					// without allocations. This is at least 100x quicker that hex.EncodeToString
 | 
				
			||||||
 | 
					// NB This requires that sha is a 40-byte slice
 | 
				
			||||||
 | 
					func to40ByteSHA(sha []byte) []byte {
 | 
				
			||||||
 | 
						for i := 19; i >= 0; i-- {
 | 
				
			||||||
 | 
							v := sha[i]
 | 
				
			||||||
 | 
							vhi, vlo := v>>4, v&0x0f
 | 
				
			||||||
 | 
							shi, slo := hextable[vhi], hextable[vlo]
 | 
				
			||||||
 | 
							sha[i*2], sha[i*2+1] = shi, slo
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return sha
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ParseTreeLineSkipMode reads an entry from a tree in a cat-file --batch stream
 | 
				
			||||||
 | 
					// This simply skips the mode - saving a substantial amount of time and carefully avoids allocations - except where fnameBuf is too small.
 | 
				
			||||||
 | 
					// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Each line is composed of:
 | 
				
			||||||
 | 
					// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <20-byte SHA>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// We don't attempt to convert the 20-byte SHA to 40-byte SHA to save a lot of time
 | 
				
			||||||
 | 
					func ParseTreeLineSkipMode(rd *bufio.Reader, fnameBuf, shaBuf []byte) (fname, sha []byte, n int, err error) {
 | 
				
			||||||
 | 
						var readBytes []byte
 | 
				
			||||||
 | 
						// Skip the Mode
 | 
				
			||||||
 | 
						readBytes, err = rd.ReadSlice(' ') // NB: DOES NOT ALLOCATE SIMPLY RETURNS SLICE WITHIN READER BUFFER
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						n += len(readBytes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Deal with the fname
 | 
				
			||||||
 | 
						readBytes, err = rd.ReadSlice('\x00')
 | 
				
			||||||
 | 
						copy(fnameBuf, readBytes)
 | 
				
			||||||
 | 
						if len(fnameBuf) > len(readBytes) {
 | 
				
			||||||
 | 
							fnameBuf = fnameBuf[:len(readBytes)] // cut the buf the correct size
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							fnameBuf = append(fnameBuf, readBytes[len(fnameBuf):]...) // extend the buf and copy in the missing bits
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for err == bufio.ErrBufferFull { // Then we need to read more
 | 
				
			||||||
 | 
							readBytes, err = rd.ReadSlice('\x00')
 | 
				
			||||||
 | 
							fnameBuf = append(fnameBuf, readBytes...) // there is little point attempting to avoid allocations here so just extend
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						n += len(fnameBuf)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fnameBuf = fnameBuf[:len(fnameBuf)-1] // Drop the terminal NUL
 | 
				
			||||||
 | 
						fname = fnameBuf                      // set the returnable fname to the slice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now deal with the 20-byte SHA
 | 
				
			||||||
 | 
						idx := 0
 | 
				
			||||||
 | 
						for idx < 20 {
 | 
				
			||||||
 | 
							read := 0
 | 
				
			||||||
 | 
							read, err = rd.Read(shaBuf[idx:20])
 | 
				
			||||||
 | 
							n += read
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							idx += read
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sha = shaBuf
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ParseTreeLine reads an entry from a tree in a cat-file --batch stream
 | 
				
			||||||
 | 
					// This carefully avoids allocations - except where fnameBuf is too small.
 | 
				
			||||||
 | 
					// It is recommended therefore to pass in an fnameBuf large enough to avoid almost all allocations
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Each line is composed of:
 | 
				
			||||||
 | 
					// <mode-in-ascii-dropping-initial-zeros> SP <fname> NUL <20-byte SHA>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// We don't attempt to convert the 20-byte SHA to 40-byte SHA to save a lot of time
 | 
				
			||||||
 | 
					func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fname, sha []byte, n int, err error) {
 | 
				
			||||||
 | 
						var readBytes []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Read the Mode
 | 
				
			||||||
 | 
						readBytes, err = rd.ReadSlice(' ')
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						n += len(readBytes)
 | 
				
			||||||
 | 
						copy(modeBuf, readBytes)
 | 
				
			||||||
 | 
						if len(modeBuf) > len(readBytes) {
 | 
				
			||||||
 | 
							modeBuf = modeBuf[:len(readBytes)]
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							modeBuf = append(modeBuf, readBytes[len(modeBuf):]...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mode = modeBuf[:len(modeBuf)-1] // Drop the SP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Deal with the fname
 | 
				
			||||||
 | 
						readBytes, err = rd.ReadSlice('\x00')
 | 
				
			||||||
 | 
						copy(fnameBuf, readBytes)
 | 
				
			||||||
 | 
						if len(fnameBuf) > len(readBytes) {
 | 
				
			||||||
 | 
							fnameBuf = fnameBuf[:len(readBytes)]
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							fnameBuf = append(fnameBuf, readBytes[len(fnameBuf):]...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for err == bufio.ErrBufferFull {
 | 
				
			||||||
 | 
							readBytes, err = rd.ReadSlice('\x00')
 | 
				
			||||||
 | 
							fnameBuf = append(fnameBuf, readBytes...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						n += len(fnameBuf)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fnameBuf = fnameBuf[:len(fnameBuf)-1]
 | 
				
			||||||
 | 
						fname = fnameBuf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Deal with the 20-byte SHA
 | 
				
			||||||
 | 
						idx := 0
 | 
				
			||||||
 | 
						for idx < 20 {
 | 
				
			||||||
 | 
							read := 0
 | 
				
			||||||
 | 
							read, err = rd.Read(shaBuf[idx:20])
 | 
				
			||||||
 | 
							n += read
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							idx += read
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sha = shaBuf
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,28 +10,9 @@ import (
 | 
				
			|||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Blob represents a Git object.
 | 
					// This file contains common functions between the gogit and !gogit variants for git Blobs
 | 
				
			||||||
type Blob struct {
 | 
					 | 
				
			||||||
	ID SHA1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gogitEncodedObj plumbing.EncodedObject
 | 
					 | 
				
			||||||
	name            string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
 | 
					 | 
				
			||||||
// Calling the Close function on the result will discard all unread output.
 | 
					 | 
				
			||||||
func (b *Blob) DataAsync() (io.ReadCloser, error) {
 | 
					 | 
				
			||||||
	return b.gogitEncodedObj.Reader()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Size returns the uncompressed size of the blob
 | 
					 | 
				
			||||||
func (b *Blob) Size() int64 {
 | 
					 | 
				
			||||||
	return b.gogitEncodedObj.Size()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Name returns name of the tree entry this blob object was created from (or empty string)
 | 
					// Name returns name of the tree entry this blob object was created from (or empty string)
 | 
				
			||||||
func (b *Blob) Name() string {
 | 
					func (b *Blob) Name() string {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								modules/git/blob_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								modules/git/blob_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Blob represents a Git object.
 | 
				
			||||||
 | 
					type Blob struct {
 | 
				
			||||||
 | 
						ID SHA1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gogitEncodedObj plumbing.EncodedObject
 | 
				
			||||||
 | 
						name            string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
 | 
				
			||||||
 | 
					// Calling the Close function on the result will discard all unread output.
 | 
				
			||||||
 | 
					func (b *Blob) DataAsync() (io.ReadCloser, error) {
 | 
				
			||||||
 | 
						return b.gogitEncodedObj.Reader()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Size returns the uncompressed size of the blob
 | 
				
			||||||
 | 
					func (b *Blob) Size() int64 {
 | 
				
			||||||
 | 
						return b.gogitEncodedObj.Size()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								modules/git/blob_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								modules/git/blob_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Blob represents a Git object.
 | 
				
			||||||
 | 
					type Blob struct {
 | 
				
			||||||
 | 
						ID SHA1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gotSize  bool
 | 
				
			||||||
 | 
						size     int64
 | 
				
			||||||
 | 
						repoPath string
 | 
				
			||||||
 | 
						name     string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
 | 
				
			||||||
 | 
					// Calling the Close function on the result will discard all unread output.
 | 
				
			||||||
 | 
					func (b *Blob) DataAsync() (io.ReadCloser, error) {
 | 
				
			||||||
 | 
						stdoutReader, stdoutWriter := io.Pipe()
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderr := &strings.Builder{}
 | 
				
			||||||
 | 
							err = NewCommand("cat-file", "--batch").RunInDirFullPipeline(b.repoPath, stdoutWriter, stderr, strings.NewReader(b.ID.String()+"\n"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								err = ConcatenateError(err, stderr.String())
 | 
				
			||||||
 | 
								_ = stdoutWriter.CloseWithError(err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bufReader := bufio.NewReader(stdoutReader)
 | 
				
			||||||
 | 
						_, _, size, err := ReadBatchLine(bufReader)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							stdoutReader.Close()
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &LimitedReaderCloser{
 | 
				
			||||||
 | 
							R: bufReader,
 | 
				
			||||||
 | 
							C: stdoutReader,
 | 
				
			||||||
 | 
							N: int64(size),
 | 
				
			||||||
 | 
						}, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Size returns the uncompressed size of the blob
 | 
				
			||||||
 | 
					func (b *Blob) Size() int64 {
 | 
				
			||||||
 | 
						if b.gotSize {
 | 
				
			||||||
 | 
							return b.size
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size, err := NewCommand("cat-file", "-s", b.ID.String()).RunInDir(b.repoPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repoPath, err)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b.size, err = strconv.ParseInt(size[:len(size)-1], 10, 64)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log("error whilst parsing size %s for %s in %s. Error: %v", size, b.ID.String(), b.repoPath, err)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						b.gotSize = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return b.size
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
// 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 git
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// LastCommitCache cache
 | 
					 | 
				
			||||||
type LastCommitCache interface {
 | 
					 | 
				
			||||||
	Get(ref, entryPath string) (*object.Commit, error)
 | 
					 | 
				
			||||||
	Put(ref, entryPath, commitID string) error
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -189,7 +189,7 @@ func (c *Command) RunInDirTimeoutEnv(env []string, timeout time.Duration, dir st
 | 
				
			|||||||
	stdout := new(bytes.Buffer)
 | 
						stdout := new(bytes.Buffer)
 | 
				
			||||||
	stderr := new(bytes.Buffer)
 | 
						stderr := new(bytes.Buffer)
 | 
				
			||||||
	if err := c.RunInDirTimeoutEnvPipeline(env, timeout, dir, stdout, stderr); err != nil {
 | 
						if err := c.RunInDirTimeoutEnvPipeline(env, timeout, dir, stdout, stderr); err != nil {
 | 
				
			||||||
		return nil, concatenateError(err, stderr.String())
 | 
							return nil, ConcatenateError(err, stderr.String())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if stdout.Len() > 0 {
 | 
						if stdout.Len() > 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,8 +19,6 @@ import (
 | 
				
			|||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Commit represents a git commit.
 | 
					// Commit represents a git commit.
 | 
				
			||||||
@@ -43,61 +41,6 @@ type CommitGPGSignature struct {
 | 
				
			|||||||
	Payload   string //TODO check if can be reconstruct from the rest of commit information to not have duplicate data
 | 
						Payload   string //TODO check if can be reconstruct from the rest of commit information to not have duplicate data
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
 | 
					 | 
				
			||||||
	if c.PGPSignature == "" {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var w strings.Builder
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, parent := range c.ParentHashes {
 | 
					 | 
				
			||||||
		if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fmt.Fprint(&w, "author "); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = c.Author.Encode(&w); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = c.Committer.Encode(&w); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &CommitGPGSignature{
 | 
					 | 
				
			||||||
		Signature: c.PGPSignature,
 | 
					 | 
				
			||||||
		Payload:   w.String(),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func convertCommit(c *object.Commit) *Commit {
 | 
					 | 
				
			||||||
	return &Commit{
 | 
					 | 
				
			||||||
		ID:            c.Hash,
 | 
					 | 
				
			||||||
		CommitMessage: c.Message,
 | 
					 | 
				
			||||||
		Committer:     &c.Committer,
 | 
					 | 
				
			||||||
		Author:        &c.Author,
 | 
					 | 
				
			||||||
		Signature:     convertPGPSignature(c),
 | 
					 | 
				
			||||||
		Parents:       c.ParentHashes,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Message returns the commit message. Same as retrieving CommitMessage directly.
 | 
					// Message returns the commit message. Same as retrieving CommitMessage directly.
 | 
				
			||||||
func (c *Commit) Message() string {
 | 
					func (c *Commit) Message() string {
 | 
				
			||||||
	return c.CommitMessage
 | 
						return c.CommitMessage
 | 
				
			||||||
@@ -576,7 +519,7 @@ func GetCommitFileStatus(repoPath, commitID string) (*CommitFileStatus, error) {
 | 
				
			|||||||
	err := NewCommand("show", "--name-status", "--pretty=format:''", commitID).RunInDirPipeline(repoPath, w, stderr)
 | 
						err := NewCommand("show", "--name-status", "--pretty=format:''", commitID).RunInDirPipeline(repoPath, w, stderr)
 | 
				
			||||||
	w.Close() // Close writer to exit parsing goroutine
 | 
						w.Close() // Close writer to exit parsing goroutine
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, concatenateError(err, stderr.String())
 | 
							return nil, ConcatenateError(err, stderr.String())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<-done
 | 
						<-done
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										70
									
								
								modules/git/commit_convert_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								modules/git/commit_convert_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2018 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func convertPGPSignature(c *object.Commit) *CommitGPGSignature {
 | 
				
			||||||
 | 
						if c.PGPSignature == "" {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var w strings.Builder
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprintf(&w, "tree %s\n", c.TreeHash.String()); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, parent := range c.ParentHashes {
 | 
				
			||||||
 | 
							if _, err = fmt.Fprintf(&w, "parent %s\n", parent.String()); err != nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprint(&w, "author "); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = c.Author.Encode(&w); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprint(&w, "\ncommitter "); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = c.Committer.Encode(&w); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprintf(&w, "\n\n%s", c.Message); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &CommitGPGSignature{
 | 
				
			||||||
 | 
							Signature: c.PGPSignature,
 | 
				
			||||||
 | 
							Payload:   w.String(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func convertCommit(c *object.Commit) *Commit {
 | 
				
			||||||
 | 
						return &Commit{
 | 
				
			||||||
 | 
							ID:            c.Hash,
 | 
				
			||||||
 | 
							CommitMessage: c.Message,
 | 
				
			||||||
 | 
							Committer:     &c.Committer,
 | 
				
			||||||
 | 
							Author:        &c.Author,
 | 
				
			||||||
 | 
							Signature:     convertPGPSignature(c),
 | 
				
			||||||
 | 
							Parents:       c.ParentHashes,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,286 +4,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					// CommitInfo describes the first commit with the provided entry
 | 
				
			||||||
	"path"
 | 
					type CommitInfo struct {
 | 
				
			||||||
 | 
						Entry         *TreeEntry
 | 
				
			||||||
	"github.com/emirpasic/gods/trees/binaryheap"
 | 
						Commit        *Commit
 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
						SubModuleFile *SubModuleFile
 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
	cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetCommitsInfo gets information of all commits that are corresponding to these entries
 | 
					 | 
				
			||||||
func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *Commit, error) {
 | 
					 | 
				
			||||||
	entryPaths := make([]string, len(tes)+1)
 | 
					 | 
				
			||||||
	// Get the commit for the treePath itself
 | 
					 | 
				
			||||||
	entryPaths[0] = ""
 | 
					 | 
				
			||||||
	for i, entry := range tes {
 | 
					 | 
				
			||||||
		entryPaths[i+1] = entry.Name()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commitNodeIndex, commitGraphFile := commit.repo.CommitNodeIndex()
 | 
					 | 
				
			||||||
	if commitGraphFile != nil {
 | 
					 | 
				
			||||||
		defer commitGraphFile.Close()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	c, err := commitNodeIndex.Get(commit.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var revs map[string]*object.Commit
 | 
					 | 
				
			||||||
	if cache != nil {
 | 
					 | 
				
			||||||
		var unHitPaths []string
 | 
					 | 
				
			||||||
		revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, cache)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if len(unHitPaths) > 0 {
 | 
					 | 
				
			||||||
			revs2, err := GetLastCommitForPaths(c, treePath, unHitPaths)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return nil, nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			for k, v := range revs2 {
 | 
					 | 
				
			||||||
				if err := cache.Put(commit.ID.String(), path.Join(treePath, k), v.ID().String()); err != nil {
 | 
					 | 
				
			||||||
					return nil, nil, err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				revs[k] = v
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		revs, err = GetLastCommitForPaths(c, treePath, entryPaths)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commit.repo.gogitStorage.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commitsInfo := make([][]interface{}, len(tes))
 | 
					 | 
				
			||||||
	for i, entry := range tes {
 | 
					 | 
				
			||||||
		if rev, ok := revs[entry.Name()]; ok {
 | 
					 | 
				
			||||||
			entryCommit := convertCommit(rev)
 | 
					 | 
				
			||||||
			if entry.IsSubModule() {
 | 
					 | 
				
			||||||
				subModuleURL := ""
 | 
					 | 
				
			||||||
				var fullPath string
 | 
					 | 
				
			||||||
				if len(treePath) > 0 {
 | 
					 | 
				
			||||||
					fullPath = treePath + "/" + entry.Name()
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					fullPath = entry.Name()
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				if subModule, err := commit.GetSubModule(fullPath); err != nil {
 | 
					 | 
				
			||||||
					return nil, nil, err
 | 
					 | 
				
			||||||
				} else if subModule != nil {
 | 
					 | 
				
			||||||
					subModuleURL = subModule.URL
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
 | 
					 | 
				
			||||||
				commitsInfo[i] = []interface{}{entry, subModuleFile}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				commitsInfo[i] = []interface{}{entry, entryCommit}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			commitsInfo[i] = []interface{}{entry, nil}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Retrieve the commit for the treePath itself (see above). We basically
 | 
					 | 
				
			||||||
	// get it for free during the tree traversal and it's used for listing
 | 
					 | 
				
			||||||
	// pages to display information about newest commit for a given path.
 | 
					 | 
				
			||||||
	var treeCommit *Commit
 | 
					 | 
				
			||||||
	if treePath == "" {
 | 
					 | 
				
			||||||
		treeCommit = commit
 | 
					 | 
				
			||||||
	} else if rev, ok := revs[""]; ok {
 | 
					 | 
				
			||||||
		treeCommit = convertCommit(rev)
 | 
					 | 
				
			||||||
		treeCommit.repo = commit.repo
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return commitsInfo, treeCommit, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type commitAndPaths struct {
 | 
					 | 
				
			||||||
	commit cgobject.CommitNode
 | 
					 | 
				
			||||||
	// Paths that are still on the branch represented by commit
 | 
					 | 
				
			||||||
	paths []string
 | 
					 | 
				
			||||||
	// Set of hashes for the paths
 | 
					 | 
				
			||||||
	hashes map[string]plumbing.Hash
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getCommitTree(c cgobject.CommitNode, treePath string) (*object.Tree, error) {
 | 
					 | 
				
			||||||
	tree, err := c.Tree()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Optimize deep traversals by focusing only on the specific tree
 | 
					 | 
				
			||||||
	if treePath != "" {
 | 
					 | 
				
			||||||
		tree, err = tree.Tree(treePath)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return tree, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[string]plumbing.Hash, error) {
 | 
					 | 
				
			||||||
	tree, err := getCommitTree(c, treePath)
 | 
					 | 
				
			||||||
	if err == object.ErrDirectoryNotFound {
 | 
					 | 
				
			||||||
		// The whole tree didn't exist, so return empty map
 | 
					 | 
				
			||||||
		return make(map[string]plumbing.Hash), nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	hashes := make(map[string]plumbing.Hash)
 | 
					 | 
				
			||||||
	for _, path := range paths {
 | 
					 | 
				
			||||||
		if path != "" {
 | 
					 | 
				
			||||||
			entry, err := tree.FindEntry(path)
 | 
					 | 
				
			||||||
			if err == nil {
 | 
					 | 
				
			||||||
				hashes[path] = entry.Hash
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			hashes[path] = tree.Hash
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return hashes, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache LastCommitCache) (map[string]*object.Commit, []string, error) {
 | 
					 | 
				
			||||||
	var unHitEntryPaths []string
 | 
					 | 
				
			||||||
	var results = make(map[string]*object.Commit)
 | 
					 | 
				
			||||||
	for _, p := range paths {
 | 
					 | 
				
			||||||
		lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if lastCommit != nil {
 | 
					 | 
				
			||||||
			results[p] = lastCommit
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		unHitEntryPaths = append(unHitEntryPaths, p)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return results, unHitEntryPaths, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetLastCommitForPaths returns last commit information
 | 
					 | 
				
			||||||
func GetLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
 | 
					 | 
				
			||||||
	// We do a tree traversal with nodes sorted by commit time
 | 
					 | 
				
			||||||
	heap := binaryheap.NewWith(func(a, b interface{}) int {
 | 
					 | 
				
			||||||
		if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) {
 | 
					 | 
				
			||||||
			return 1
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return -1
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	resultNodes := make(map[string]cgobject.CommitNode)
 | 
					 | 
				
			||||||
	initialHashes, err := getFileHashes(c, treePath, paths)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Start search from the root commit and with full set of paths
 | 
					 | 
				
			||||||
	heap.Push(&commitAndPaths{c, paths, initialHashes})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for {
 | 
					 | 
				
			||||||
		cIn, ok := heap.Pop()
 | 
					 | 
				
			||||||
		if !ok {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		current := cIn.(*commitAndPaths)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Load the parent commits for the one we are currently examining
 | 
					 | 
				
			||||||
		numParents := current.commit.NumParents()
 | 
					 | 
				
			||||||
		var parents []cgobject.CommitNode
 | 
					 | 
				
			||||||
		for i := 0; i < numParents; i++ {
 | 
					 | 
				
			||||||
			parent, err := current.commit.ParentNode(i)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			parents = append(parents, parent)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Examine the current commit and set of interesting paths
 | 
					 | 
				
			||||||
		pathUnchanged := make([]bool, len(current.paths))
 | 
					 | 
				
			||||||
		parentHashes := make([]map[string]plumbing.Hash, len(parents))
 | 
					 | 
				
			||||||
		for j, parent := range parents {
 | 
					 | 
				
			||||||
			parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			for i, path := range current.paths {
 | 
					 | 
				
			||||||
				if parentHashes[j][path] == current.hashes[path] {
 | 
					 | 
				
			||||||
					pathUnchanged[i] = true
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var remainingPaths []string
 | 
					 | 
				
			||||||
		for i, path := range current.paths {
 | 
					 | 
				
			||||||
			// The results could already contain some newer change for the same path,
 | 
					 | 
				
			||||||
			// so don't override that and bail out on the file early.
 | 
					 | 
				
			||||||
			if resultNodes[path] == nil {
 | 
					 | 
				
			||||||
				if pathUnchanged[i] {
 | 
					 | 
				
			||||||
					// The path existed with the same hash in at least one parent so it could
 | 
					 | 
				
			||||||
					// not have been changed in this commit directly.
 | 
					 | 
				
			||||||
					remainingPaths = append(remainingPaths, path)
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					// There are few possible cases how can we get here:
 | 
					 | 
				
			||||||
					// - The path didn't exist in any parent, so it must have been created by
 | 
					 | 
				
			||||||
					//   this commit.
 | 
					 | 
				
			||||||
					// - The path did exist in the parent commit, but the hash of the file has
 | 
					 | 
				
			||||||
					//   changed.
 | 
					 | 
				
			||||||
					// - We are looking at a merge commit and the hash of the file doesn't
 | 
					 | 
				
			||||||
					//   match any of the hashes being merged. This is more common for directories,
 | 
					 | 
				
			||||||
					//   but it can also happen if a file is changed through conflict resolution.
 | 
					 | 
				
			||||||
					resultNodes[path] = current.commit
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if len(remainingPaths) > 0 {
 | 
					 | 
				
			||||||
			// Add the parent nodes along with remaining paths to the heap for further
 | 
					 | 
				
			||||||
			// processing.
 | 
					 | 
				
			||||||
			for j, parent := range parents {
 | 
					 | 
				
			||||||
				// Combine remainingPath with paths available on the parent branch
 | 
					 | 
				
			||||||
				// and make union of them
 | 
					 | 
				
			||||||
				remainingPathsForParent := make([]string, 0, len(remainingPaths))
 | 
					 | 
				
			||||||
				newRemainingPaths := make([]string, 0, len(remainingPaths))
 | 
					 | 
				
			||||||
				for _, path := range remainingPaths {
 | 
					 | 
				
			||||||
					if parentHashes[j][path] == current.hashes[path] {
 | 
					 | 
				
			||||||
						remainingPathsForParent = append(remainingPathsForParent, path)
 | 
					 | 
				
			||||||
					} else {
 | 
					 | 
				
			||||||
						newRemainingPaths = append(newRemainingPaths, path)
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if remainingPathsForParent != nil {
 | 
					 | 
				
			||||||
					heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if len(newRemainingPaths) == 0 {
 | 
					 | 
				
			||||||
					break
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					remainingPaths = newRemainingPaths
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Post-processing
 | 
					 | 
				
			||||||
	result := make(map[string]*object.Commit)
 | 
					 | 
				
			||||||
	for path, commitNode := range resultNodes {
 | 
					 | 
				
			||||||
		var err error
 | 
					 | 
				
			||||||
		result[path], err = commitNode.Commit()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return result, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										291
									
								
								modules/git/commit_info_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								modules/git/commit_info_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,291 @@
 | 
				
			|||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/emirpasic/gods/trees/binaryheap"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
						cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCommitsInfo gets information of all commits that are corresponding to these entries
 | 
				
			||||||
 | 
					func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache *LastCommitCache) ([]CommitInfo, *Commit, error) {
 | 
				
			||||||
 | 
						entryPaths := make([]string, len(tes)+1)
 | 
				
			||||||
 | 
						// Get the commit for the treePath itself
 | 
				
			||||||
 | 
						entryPaths[0] = ""
 | 
				
			||||||
 | 
						for i, entry := range tes {
 | 
				
			||||||
 | 
							entryPaths[i+1] = entry.Name()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitNodeIndex, commitGraphFile := commit.repo.CommitNodeIndex()
 | 
				
			||||||
 | 
						if commitGraphFile != nil {
 | 
				
			||||||
 | 
							defer commitGraphFile.Close()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c, err := commitNodeIndex.Get(commit.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var revs map[string]*object.Commit
 | 
				
			||||||
 | 
						if cache != nil {
 | 
				
			||||||
 | 
							var unHitPaths []string
 | 
				
			||||||
 | 
							revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, cache)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(unHitPaths) > 0 {
 | 
				
			||||||
 | 
								revs2, err := GetLastCommitForPaths(c, treePath, unHitPaths)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for k, v := range revs2 {
 | 
				
			||||||
 | 
									if err := cache.Put(commit.ID.String(), path.Join(treePath, k), v.ID().String()); err != nil {
 | 
				
			||||||
 | 
										return nil, nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									revs[k] = v
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							revs, err = GetLastCommitForPaths(c, treePath, entryPaths)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit.repo.gogitStorage.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitsInfo := make([]CommitInfo, len(tes))
 | 
				
			||||||
 | 
						for i, entry := range tes {
 | 
				
			||||||
 | 
							commitsInfo[i] = CommitInfo{
 | 
				
			||||||
 | 
								Entry: entry,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if rev, ok := revs[entry.Name()]; ok {
 | 
				
			||||||
 | 
								entryCommit := convertCommit(rev)
 | 
				
			||||||
 | 
								commitsInfo[i].Commit = entryCommit
 | 
				
			||||||
 | 
								if entry.IsSubModule() {
 | 
				
			||||||
 | 
									subModuleURL := ""
 | 
				
			||||||
 | 
									var fullPath string
 | 
				
			||||||
 | 
									if len(treePath) > 0 {
 | 
				
			||||||
 | 
										fullPath = treePath + "/" + entry.Name()
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										fullPath = entry.Name()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if subModule, err := commit.GetSubModule(fullPath); err != nil {
 | 
				
			||||||
 | 
										return nil, nil, err
 | 
				
			||||||
 | 
									} else if subModule != nil {
 | 
				
			||||||
 | 
										subModuleURL = subModule.URL
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
 | 
				
			||||||
 | 
									commitsInfo[i].SubModuleFile = subModuleFile
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Retrieve the commit for the treePath itself (see above). We basically
 | 
				
			||||||
 | 
						// get it for free during the tree traversal and it's used for listing
 | 
				
			||||||
 | 
						// pages to display information about newest commit for a given path.
 | 
				
			||||||
 | 
						var treeCommit *Commit
 | 
				
			||||||
 | 
						if treePath == "" {
 | 
				
			||||||
 | 
							treeCommit = commit
 | 
				
			||||||
 | 
						} else if rev, ok := revs[""]; ok {
 | 
				
			||||||
 | 
							treeCommit = convertCommit(rev)
 | 
				
			||||||
 | 
							treeCommit.repo = commit.repo
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return commitsInfo, treeCommit, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type commitAndPaths struct {
 | 
				
			||||||
 | 
						commit cgobject.CommitNode
 | 
				
			||||||
 | 
						// Paths that are still on the branch represented by commit
 | 
				
			||||||
 | 
						paths []string
 | 
				
			||||||
 | 
						// Set of hashes for the paths
 | 
				
			||||||
 | 
						hashes map[string]plumbing.Hash
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getCommitTree(c cgobject.CommitNode, treePath string) (*object.Tree, error) {
 | 
				
			||||||
 | 
						tree, err := c.Tree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Optimize deep traversals by focusing only on the specific tree
 | 
				
			||||||
 | 
						if treePath != "" {
 | 
				
			||||||
 | 
							tree, err = tree.Tree(treePath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return tree, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[string]plumbing.Hash, error) {
 | 
				
			||||||
 | 
						tree, err := getCommitTree(c, treePath)
 | 
				
			||||||
 | 
						if err == object.ErrDirectoryNotFound {
 | 
				
			||||||
 | 
							// The whole tree didn't exist, so return empty map
 | 
				
			||||||
 | 
							return make(map[string]plumbing.Hash), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hashes := make(map[string]plumbing.Hash)
 | 
				
			||||||
 | 
						for _, path := range paths {
 | 
				
			||||||
 | 
							if path != "" {
 | 
				
			||||||
 | 
								entry, err := tree.FindEntry(path)
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									hashes[path] = entry.Hash
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								hashes[path] = tree.Hash
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hashes, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*object.Commit, []string, error) {
 | 
				
			||||||
 | 
						var unHitEntryPaths []string
 | 
				
			||||||
 | 
						var results = make(map[string]*object.Commit)
 | 
				
			||||||
 | 
						for _, p := range paths {
 | 
				
			||||||
 | 
							lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if lastCommit != nil {
 | 
				
			||||||
 | 
								results[p] = lastCommit.(*object.Commit)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							unHitEntryPaths = append(unHitEntryPaths, p)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return results, unHitEntryPaths, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLastCommitForPaths returns last commit information
 | 
				
			||||||
 | 
					func GetLastCommitForPaths(c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
 | 
				
			||||||
 | 
						// We do a tree traversal with nodes sorted by commit time
 | 
				
			||||||
 | 
						heap := binaryheap.NewWith(func(a, b interface{}) int {
 | 
				
			||||||
 | 
							if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) {
 | 
				
			||||||
 | 
								return 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return -1
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resultNodes := make(map[string]cgobject.CommitNode)
 | 
				
			||||||
 | 
						initialHashes, err := getFileHashes(c, treePath, paths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start search from the root commit and with full set of paths
 | 
				
			||||||
 | 
						heap.Push(&commitAndPaths{c, paths, initialHashes})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							cIn, ok := heap.Pop()
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							current := cIn.(*commitAndPaths)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Load the parent commits for the one we are currently examining
 | 
				
			||||||
 | 
							numParents := current.commit.NumParents()
 | 
				
			||||||
 | 
							var parents []cgobject.CommitNode
 | 
				
			||||||
 | 
							for i := 0; i < numParents; i++ {
 | 
				
			||||||
 | 
								parent, err := current.commit.ParentNode(i)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								parents = append(parents, parent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Examine the current commit and set of interesting paths
 | 
				
			||||||
 | 
							pathUnchanged := make([]bool, len(current.paths))
 | 
				
			||||||
 | 
							parentHashes := make([]map[string]plumbing.Hash, len(parents))
 | 
				
			||||||
 | 
							for j, parent := range parents {
 | 
				
			||||||
 | 
								parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for i, path := range current.paths {
 | 
				
			||||||
 | 
									if parentHashes[j][path] == current.hashes[path] {
 | 
				
			||||||
 | 
										pathUnchanged[i] = true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var remainingPaths []string
 | 
				
			||||||
 | 
							for i, path := range current.paths {
 | 
				
			||||||
 | 
								// The results could already contain some newer change for the same path,
 | 
				
			||||||
 | 
								// so don't override that and bail out on the file early.
 | 
				
			||||||
 | 
								if resultNodes[path] == nil {
 | 
				
			||||||
 | 
									if pathUnchanged[i] {
 | 
				
			||||||
 | 
										// The path existed with the same hash in at least one parent so it could
 | 
				
			||||||
 | 
										// not have been changed in this commit directly.
 | 
				
			||||||
 | 
										remainingPaths = append(remainingPaths, path)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										// There are few possible cases how can we get here:
 | 
				
			||||||
 | 
										// - The path didn't exist in any parent, so it must have been created by
 | 
				
			||||||
 | 
										//   this commit.
 | 
				
			||||||
 | 
										// - The path did exist in the parent commit, but the hash of the file has
 | 
				
			||||||
 | 
										//   changed.
 | 
				
			||||||
 | 
										// - We are looking at a merge commit and the hash of the file doesn't
 | 
				
			||||||
 | 
										//   match any of the hashes being merged. This is more common for directories,
 | 
				
			||||||
 | 
										//   but it can also happen if a file is changed through conflict resolution.
 | 
				
			||||||
 | 
										resultNodes[path] = current.commit
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(remainingPaths) > 0 {
 | 
				
			||||||
 | 
								// Add the parent nodes along with remaining paths to the heap for further
 | 
				
			||||||
 | 
								// processing.
 | 
				
			||||||
 | 
								for j, parent := range parents {
 | 
				
			||||||
 | 
									// Combine remainingPath with paths available on the parent branch
 | 
				
			||||||
 | 
									// and make union of them
 | 
				
			||||||
 | 
									remainingPathsForParent := make([]string, 0, len(remainingPaths))
 | 
				
			||||||
 | 
									newRemainingPaths := make([]string, 0, len(remainingPaths))
 | 
				
			||||||
 | 
									for _, path := range remainingPaths {
 | 
				
			||||||
 | 
										if parentHashes[j][path] == current.hashes[path] {
 | 
				
			||||||
 | 
											remainingPathsForParent = append(remainingPathsForParent, path)
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											newRemainingPaths = append(newRemainingPaths, path)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if remainingPathsForParent != nil {
 | 
				
			||||||
 | 
										heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if len(newRemainingPaths) == 0 {
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										remainingPaths = newRemainingPaths
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Post-processing
 | 
				
			||||||
 | 
						result := make(map[string]*object.Commit)
 | 
				
			||||||
 | 
						for path, commitNode := range resultNodes {
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							result[path], err = commitNode.Commit()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										370
									
								
								modules/git/commit_info_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								modules/git/commit_info_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,370 @@
 | 
				
			|||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCommitsInfo gets information of all commits that are corresponding to these entries
 | 
				
			||||||
 | 
					func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache *LastCommitCache) ([]CommitInfo, *Commit, error) {
 | 
				
			||||||
 | 
						entryPaths := make([]string, len(tes)+1)
 | 
				
			||||||
 | 
						// Get the commit for the treePath itself
 | 
				
			||||||
 | 
						entryPaths[0] = ""
 | 
				
			||||||
 | 
						for i, entry := range tes {
 | 
				
			||||||
 | 
							entryPaths[i+1] = entry.Name()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var revs map[string]*Commit
 | 
				
			||||||
 | 
						if cache != nil {
 | 
				
			||||||
 | 
							var unHitPaths []string
 | 
				
			||||||
 | 
							revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, cache)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(unHitPaths) > 0 {
 | 
				
			||||||
 | 
								sort.Strings(unHitPaths)
 | 
				
			||||||
 | 
								commits, err := GetLastCommitForPaths(commit, treePath, unHitPaths)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for i, found := range commits {
 | 
				
			||||||
 | 
									if err := cache.Put(commit.ID.String(), path.Join(treePath, unHitPaths[i]), found.ID.String()); err != nil {
 | 
				
			||||||
 | 
										return nil, nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									revs[unHitPaths[i]] = found
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							sort.Strings(entryPaths)
 | 
				
			||||||
 | 
							revs = map[string]*Commit{}
 | 
				
			||||||
 | 
							var foundCommits []*Commit
 | 
				
			||||||
 | 
							foundCommits, err = GetLastCommitForPaths(commit, treePath, entryPaths)
 | 
				
			||||||
 | 
							for i, found := range foundCommits {
 | 
				
			||||||
 | 
								revs[entryPaths[i]] = found
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitsInfo := make([]CommitInfo, len(tes))
 | 
				
			||||||
 | 
						for i, entry := range tes {
 | 
				
			||||||
 | 
							commitsInfo[i] = CommitInfo{
 | 
				
			||||||
 | 
								Entry: entry,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entryCommit, ok := revs[entry.Name()]; ok {
 | 
				
			||||||
 | 
								commitsInfo[i].Commit = entryCommit
 | 
				
			||||||
 | 
								if entry.IsSubModule() {
 | 
				
			||||||
 | 
									subModuleURL := ""
 | 
				
			||||||
 | 
									var fullPath string
 | 
				
			||||||
 | 
									if len(treePath) > 0 {
 | 
				
			||||||
 | 
										fullPath = treePath + "/" + entry.Name()
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										fullPath = entry.Name()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if subModule, err := commit.GetSubModule(fullPath); err != nil {
 | 
				
			||||||
 | 
										return nil, nil, err
 | 
				
			||||||
 | 
									} else if subModule != nil {
 | 
				
			||||||
 | 
										subModuleURL = subModule.URL
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
 | 
				
			||||||
 | 
									commitsInfo[i].SubModuleFile = subModuleFile
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Retrieve the commit for the treePath itself (see above). We basically
 | 
				
			||||||
 | 
						// get it for free during the tree traversal and it's used for listing
 | 
				
			||||||
 | 
						// pages to display information about newest commit for a given path.
 | 
				
			||||||
 | 
						var treeCommit *Commit
 | 
				
			||||||
 | 
						var ok bool
 | 
				
			||||||
 | 
						if treePath == "" {
 | 
				
			||||||
 | 
							treeCommit = commit
 | 
				
			||||||
 | 
						} else if treeCommit, ok = revs[""]; ok {
 | 
				
			||||||
 | 
							treeCommit.repo = commit.repo
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return commitsInfo, treeCommit, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
 | 
				
			||||||
 | 
						var unHitEntryPaths []string
 | 
				
			||||||
 | 
						var results = make(map[string]*Commit)
 | 
				
			||||||
 | 
						for _, p := range paths {
 | 
				
			||||||
 | 
							lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if lastCommit != nil {
 | 
				
			||||||
 | 
								results[p] = lastCommit.(*Commit)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							unHitEntryPaths = append(unHitEntryPaths, p)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return results, unHitEntryPaths, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLastCommitForPaths returns last commit information
 | 
				
			||||||
 | 
					func GetLastCommitForPaths(commit *Commit, treePath string, paths []string) ([]*Commit, error) {
 | 
				
			||||||
 | 
						// We read backwards from the commit to obtain all of the commits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We'll do this by using rev-list to provide us with parent commits in order
 | 
				
			||||||
 | 
						revListReader, revListWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = revListWriter.Close()
 | 
				
			||||||
 | 
							_ = revListReader.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderr := strings.Builder{}
 | 
				
			||||||
 | 
							err := NewCommand("rev-list", "--format=%T", commit.ID.String()).RunInDirPipeline(commit.repo.Path, revListWriter, &stderr)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = revListWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = revListWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
 | 
				
			||||||
 | 
						// so let's create a batch stdin and stdout
 | 
				
			||||||
 | 
						batchStdinReader, batchStdinWriter := io.Pipe()
 | 
				
			||||||
 | 
						batchStdoutReader, batchStdoutWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = batchStdinReader.Close()
 | 
				
			||||||
 | 
							_ = batchStdinWriter.Close()
 | 
				
			||||||
 | 
							_ = batchStdoutReader.Close()
 | 
				
			||||||
 | 
							_ = batchStdoutWriter.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderr := strings.Builder{}
 | 
				
			||||||
 | 
							err := NewCommand("cat-file", "--batch").RunInDirFullPipeline(commit.repo.Path, batchStdoutWriter, &stderr, batchStdinReader)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = revListWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = revListWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// For simplicities sake we'll us a buffered reader
 | 
				
			||||||
 | 
						batchReader := bufio.NewReader(batchStdoutReader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mapsize := 4096
 | 
				
			||||||
 | 
						if len(paths) > mapsize {
 | 
				
			||||||
 | 
							mapsize = len(paths)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						path2idx := make(map[string]int, mapsize)
 | 
				
			||||||
 | 
						for i, path := range paths {
 | 
				
			||||||
 | 
							path2idx[path] = i
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fnameBuf := make([]byte, 4096)
 | 
				
			||||||
 | 
						modeBuf := make([]byte, 40)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						allShaBuf := make([]byte, (len(paths)+1)*20)
 | 
				
			||||||
 | 
						shaBuf := make([]byte, 20)
 | 
				
			||||||
 | 
						tmpTreeID := make([]byte, 40)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// commits is the returnable commits matching the paths provided
 | 
				
			||||||
 | 
						commits := make([]string, len(paths))
 | 
				
			||||||
 | 
						// ids are the blob/tree ids for the paths
 | 
				
			||||||
 | 
						ids := make([][]byte, len(paths))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We'll use a scanner for the revList because it's simpler than a bufio.Reader
 | 
				
			||||||
 | 
						scan := bufio.NewScanner(revListReader)
 | 
				
			||||||
 | 
					revListLoop:
 | 
				
			||||||
 | 
						for scan.Scan() {
 | 
				
			||||||
 | 
							// Get the next parent commit ID
 | 
				
			||||||
 | 
							commitID := scan.Text()
 | 
				
			||||||
 | 
							if !scan.Scan() {
 | 
				
			||||||
 | 
								break revListLoop
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							commitID = commitID[7:]
 | 
				
			||||||
 | 
							rootTreeID := scan.Text()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// push the tree to the cat-file --batch process
 | 
				
			||||||
 | 
							_, err := batchStdinWriter.Write([]byte(rootTreeID + "\n"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							currentPath := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// OK if the target tree path is "" and the "" is in the paths just set this now
 | 
				
			||||||
 | 
							if treePath == "" && paths[0] == "" {
 | 
				
			||||||
 | 
								// If this is the first time we see this set the id appropriate for this paths to this tree and set the last commit to curCommit
 | 
				
			||||||
 | 
								if len(ids[0]) == 0 {
 | 
				
			||||||
 | 
									ids[0] = []byte(rootTreeID)
 | 
				
			||||||
 | 
									commits[0] = string(commitID)
 | 
				
			||||||
 | 
								} else if bytes.Equal(ids[0], []byte(rootTreeID)) {
 | 
				
			||||||
 | 
									commits[0] = string(commitID)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						treeReadingLoop:
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								_, _, size, err := ReadBatchLine(batchReader)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Handle trees
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// n is counter for file position in the tree file
 | 
				
			||||||
 | 
								var n int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Two options: currentPath is the targetTreepath
 | 
				
			||||||
 | 
								if treePath == currentPath {
 | 
				
			||||||
 | 
									// We are in the right directory
 | 
				
			||||||
 | 
									// Parse each tree line in turn. (don't care about mode here.)
 | 
				
			||||||
 | 
									for n < size {
 | 
				
			||||||
 | 
										fname, sha, count, err := ParseTreeLineSkipMode(batchReader, fnameBuf, shaBuf)
 | 
				
			||||||
 | 
										shaBuf = sha
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return nil, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										n += int64(count)
 | 
				
			||||||
 | 
										idx, ok := path2idx[string(fname)]
 | 
				
			||||||
 | 
										if ok {
 | 
				
			||||||
 | 
											// Now if this is the first time round set the initial Blob(ish) SHA ID and the commit
 | 
				
			||||||
 | 
											if len(ids[idx]) == 0 {
 | 
				
			||||||
 | 
												copy(allShaBuf[20*(idx+1):20*(idx+2)], shaBuf)
 | 
				
			||||||
 | 
												ids[idx] = allShaBuf[20*(idx+1) : 20*(idx+2)]
 | 
				
			||||||
 | 
												commits[idx] = string(commitID)
 | 
				
			||||||
 | 
											} else if bytes.Equal(ids[idx], shaBuf) {
 | 
				
			||||||
 | 
												commits[idx] = string(commitID)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// FIXME: is there any order to the way strings are emitted from cat-file?
 | 
				
			||||||
 | 
										// if there is - then we could skip once we've passed all of our data
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									break treeReadingLoop
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var treeID []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// We're in the wrong directory
 | 
				
			||||||
 | 
								// Find target directory in this directory
 | 
				
			||||||
 | 
								idx := len(currentPath)
 | 
				
			||||||
 | 
								if idx > 0 {
 | 
				
			||||||
 | 
									idx++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								target := strings.SplitN(treePath[idx:], "/", 2)[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for n < size {
 | 
				
			||||||
 | 
									// Read each tree entry in turn
 | 
				
			||||||
 | 
									mode, fname, sha, count, err := ParseTreeLine(batchReader, modeBuf, fnameBuf, shaBuf)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									n += int64(count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// if we have found the target directory
 | 
				
			||||||
 | 
									if bytes.Equal(fname, []byte(target)) && bytes.Equal(mode, []byte("40000")) {
 | 
				
			||||||
 | 
										copy(tmpTreeID, sha)
 | 
				
			||||||
 | 
										treeID = tmpTreeID
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if n < size {
 | 
				
			||||||
 | 
									// Discard any remaining entries in the current tree
 | 
				
			||||||
 | 
									discard := size - n
 | 
				
			||||||
 | 
									for discard > math.MaxInt32 {
 | 
				
			||||||
 | 
										_, err := batchReader.Discard(math.MaxInt32)
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return nil, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										discard -= math.MaxInt32
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									_, err := batchReader.Discard(int(discard))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// if we haven't found a treeID for the target directory our search is over
 | 
				
			||||||
 | 
								if len(treeID) == 0 {
 | 
				
			||||||
 | 
									break treeReadingLoop
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// add the target to the current path
 | 
				
			||||||
 | 
								if idx > 0 {
 | 
				
			||||||
 | 
									currentPath += "/"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								currentPath += target
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// if we've now found the current path check its sha id and commit status
 | 
				
			||||||
 | 
								if treePath == currentPath && paths[0] == "" {
 | 
				
			||||||
 | 
									if len(ids[0]) == 0 {
 | 
				
			||||||
 | 
										copy(allShaBuf[0:20], treeID)
 | 
				
			||||||
 | 
										ids[0] = allShaBuf[0:20]
 | 
				
			||||||
 | 
										commits[0] = string(commitID)
 | 
				
			||||||
 | 
									} else if bytes.Equal(ids[0], treeID) {
 | 
				
			||||||
 | 
										commits[0] = string(commitID)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								treeID = to40ByteSHA(treeID)
 | 
				
			||||||
 | 
								_, err = batchStdinWriter.Write(treeID)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_, err = batchStdinWriter.Write([]byte("\n"))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitsMap := make(map[string]*Commit, len(commits))
 | 
				
			||||||
 | 
						commitsMap[commit.ID.String()] = commit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitCommits := make([]*Commit, len(commits))
 | 
				
			||||||
 | 
						for i, commitID := range commits {
 | 
				
			||||||
 | 
							c, ok := commitsMap[commitID]
 | 
				
			||||||
 | 
							if ok {
 | 
				
			||||||
 | 
								commitCommits[i] = c
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(commitID) == 0 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_, err := batchStdinWriter.Write([]byte(commitID + "\n"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, typ, size, err := ReadBatchLine(batchReader)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if typ != "commit" {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c, err = CommitFromReader(commit.repo, MustIDFromString(string(commitID)), io.LimitReader(batchReader, int64(size)))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							commitCommits[i] = c
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return commitCommits, scan.Err()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -58,17 +58,27 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
 | 
				
			|||||||
	for _, testCase := range testCases {
 | 
						for _, testCase := range testCases {
 | 
				
			||||||
		commit, err := repo1.GetCommit(testCase.CommitID)
 | 
							commit, err := repo1.GetCommit(testCase.CommitID)
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							assert.NotNil(t, commit)
 | 
				
			||||||
 | 
							assert.NotNil(t, commit.Tree)
 | 
				
			||||||
 | 
							assert.NotNil(t, commit.Tree.repo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tree, err := commit.Tree.SubTree(testCase.Path)
 | 
							tree, err := commit.Tree.SubTree(testCase.Path)
 | 
				
			||||||
 | 
							assert.NotNil(t, tree, "tree is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
 | 
				
			||||||
 | 
							assert.NotNil(t, tree.repo, "repo is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
		entries, err := tree.ListEntries()
 | 
							entries, err := tree.ListEntries()
 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
		commitsInfo, treeCommit, err := entries.GetCommitsInfo(commit, testCase.Path, nil)
 | 
							commitsInfo, treeCommit, err := entries.GetCommitsInfo(commit, testCase.Path, nil)
 | 
				
			||||||
		assert.Equal(t, testCase.ExpectedTreeCommit, treeCommit.ID.String())
 | 
					 | 
				
			||||||
		assert.NoError(t, err)
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.FailNow()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							assert.Equal(t, testCase.ExpectedTreeCommit, treeCommit.ID.String())
 | 
				
			||||||
		assert.Len(t, commitsInfo, len(testCase.ExpectedIDs))
 | 
							assert.Len(t, commitsInfo, len(testCase.ExpectedIDs))
 | 
				
			||||||
		for _, commitInfo := range commitsInfo {
 | 
							for _, commitInfo := range commitsInfo {
 | 
				
			||||||
			entry := commitInfo[0].(*TreeEntry)
 | 
								entry := commitInfo.Entry
 | 
				
			||||||
			commit := commitInfo[1].(*Commit)
 | 
								commit := commitInfo.Commit
 | 
				
			||||||
			expectedID, ok := testCase.ExpectedIDs[entry.Name()]
 | 
								expectedID, ok := testCase.ExpectedIDs[entry.Name()]
 | 
				
			||||||
			if !assert.True(t, ok) {
 | 
								if !assert.True(t, ok) {
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,13 +9,13 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CommitFromReader will generate a Commit from a provided reader
 | 
					// CommitFromReader will generate a Commit from a provided reader
 | 
				
			||||||
// We will need this to interpret commits from cat-file
 | 
					// We need this to interpret commits from cat-file or cat-file --batch
 | 
				
			||||||
func CommitFromReader(gitRepo *Repository, sha plumbing.Hash, reader io.Reader) (*Commit, error) {
 | 
					//
 | 
				
			||||||
 | 
					// If used as part of a cat-file --batch stream you need to limit the reader to the correct size
 | 
				
			||||||
 | 
					func CommitFromReader(gitRepo *Repository, sha SHA1, reader io.Reader) (*Commit, error) {
 | 
				
			||||||
	commit := &Commit{
 | 
						commit := &Commit{
 | 
				
			||||||
		ID: sha,
 | 
							ID: sha,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -26,26 +26,20 @@ func CommitFromReader(gitRepo *Repository, sha plumbing.Hash, reader io.Reader)
 | 
				
			|||||||
	message := false
 | 
						message := false
 | 
				
			||||||
	pgpsig := false
 | 
						pgpsig := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	scanner := bufio.NewScanner(reader)
 | 
						bufReader, ok := reader.(*bufio.Reader)
 | 
				
			||||||
	// Split by '\n' but include the '\n'
 | 
						if !ok {
 | 
				
			||||||
	scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
 | 
							bufReader = bufio.NewReader(reader)
 | 
				
			||||||
		if atEOF && len(data) == 0 {
 | 
						}
 | 
				
			||||||
			return 0, nil, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if i := bytes.IndexByte(data, '\n'); i >= 0 {
 | 
					 | 
				
			||||||
			// We have a full newline-terminated line.
 | 
					 | 
				
			||||||
			return i + 1, data[0 : i+1], nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		// If we're at EOF, we have a final, non-terminated line. Return it.
 | 
					 | 
				
			||||||
		if atEOF {
 | 
					 | 
				
			||||||
			return len(data), data, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		// Request more data.
 | 
					 | 
				
			||||||
		return 0, nil, nil
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for scanner.Scan() {
 | 
					readLoop:
 | 
				
			||||||
		line := scanner.Bytes()
 | 
						for {
 | 
				
			||||||
 | 
							line, err := bufReader.ReadBytes('\n')
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if err == io.EOF {
 | 
				
			||||||
 | 
									break readLoop
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		if pgpsig {
 | 
							if pgpsig {
 | 
				
			||||||
			if len(line) > 0 && line[0] == ' ' {
 | 
								if len(line) > 0 && line[0] == ' ' {
 | 
				
			||||||
				_, _ = signatureSB.Write(line[1:])
 | 
									_, _ = signatureSB.Write(line[1:])
 | 
				
			||||||
@@ -72,10 +66,10 @@ func CommitFromReader(gitRepo *Repository, sha plumbing.Hash, reader io.Reader)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			switch string(split[0]) {
 | 
								switch string(split[0]) {
 | 
				
			||||||
			case "tree":
 | 
								case "tree":
 | 
				
			||||||
				commit.Tree = *NewTree(gitRepo, plumbing.NewHash(string(data)))
 | 
									commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data)))
 | 
				
			||||||
				_, _ = payloadSB.Write(line)
 | 
									_, _ = payloadSB.Write(line)
 | 
				
			||||||
			case "parent":
 | 
								case "parent":
 | 
				
			||||||
				commit.Parents = append(commit.Parents, plumbing.NewHash(string(data)))
 | 
									commit.Parents = append(commit.Parents, MustIDFromString(string(data)))
 | 
				
			||||||
				_, _ = payloadSB.Write(line)
 | 
									_, _ = payloadSB.Write(line)
 | 
				
			||||||
			case "author":
 | 
								case "author":
 | 
				
			||||||
				commit.Author = &Signature{}
 | 
									commit.Author = &Signature{}
 | 
				
			||||||
@@ -104,5 +98,5 @@ func CommitFromReader(gitRepo *Repository, sha plumbing.Hash, reader io.Reader)
 | 
				
			|||||||
		commit.Signature = nil
 | 
							commit.Signature = nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return commit, scanner.Err()
 | 
						return commit, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								modules/git/last_commit_cache.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								modules/git/last_commit_cache.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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 git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/sha256"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Cache represents a caching interface
 | 
				
			||||||
 | 
					type Cache interface {
 | 
				
			||||||
 | 
						// Put puts value into cache with key and expire time.
 | 
				
			||||||
 | 
						Put(key string, val interface{}, timeout int64) error
 | 
				
			||||||
 | 
						// Get gets cached value by given key.
 | 
				
			||||||
 | 
						Get(key string) interface{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LastCommitCache) getCacheKey(repoPath, ref, entryPath string) string {
 | 
				
			||||||
 | 
						hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, ref, entryPath)))
 | 
				
			||||||
 | 
						return fmt.Sprintf("last_commit:%x", hashBytes)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Put put the last commit id with commit and entry path
 | 
				
			||||||
 | 
					func (c *LastCommitCache) Put(ref, entryPath, commitID string) error {
 | 
				
			||||||
 | 
						log("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
 | 
				
			||||||
 | 
						return c.cache.Put(c.getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										113
									
								
								modules/git/last_commit_cache_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								modules/git/last_commit_cache_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
						cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LastCommitCache represents a cache to store last commit
 | 
				
			||||||
 | 
					type LastCommitCache struct {
 | 
				
			||||||
 | 
						repoPath    string
 | 
				
			||||||
 | 
						ttl         int64
 | 
				
			||||||
 | 
						repo        *Repository
 | 
				
			||||||
 | 
						commitCache map[string]*object.Commit
 | 
				
			||||||
 | 
						cache       Cache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewLastCommitCache creates a new last commit cache for repo
 | 
				
			||||||
 | 
					func NewLastCommitCache(repoPath string, gitRepo *Repository, ttl int64, cache Cache) *LastCommitCache {
 | 
				
			||||||
 | 
						if cache == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &LastCommitCache{
 | 
				
			||||||
 | 
							repoPath:    repoPath,
 | 
				
			||||||
 | 
							repo:        gitRepo,
 | 
				
			||||||
 | 
							commitCache: make(map[string]*object.Commit),
 | 
				
			||||||
 | 
							ttl:         ttl,
 | 
				
			||||||
 | 
							cache:       cache,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get get the last commit information by commit id and entry path
 | 
				
			||||||
 | 
					func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) {
 | 
				
			||||||
 | 
						v := c.cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
 | 
				
			||||||
 | 
						if vs, ok := v.(string); ok {
 | 
				
			||||||
 | 
							log("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
 | 
				
			||||||
 | 
							if commit, ok := c.commitCache[vs]; ok {
 | 
				
			||||||
 | 
								log("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
 | 
				
			||||||
 | 
								return commit, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							id, err := c.repo.ConvertToSHA1(vs)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							commit, err := c.repo.GoGitRepo().CommitObject(id)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.commitCache[vs] = commit
 | 
				
			||||||
 | 
							return commit, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CacheCommit will cache the commit from the gitRepository
 | 
				
			||||||
 | 
					func (c *LastCommitCache) CacheCommit(commit *Commit) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitNodeIndex, _ := commit.repo.CommitNodeIndex()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						index, err := commitNodeIndex.Get(commit.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.recursiveCache(index, &commit.Tree, "", 1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LastCommitCache) recursiveCache(index cgobject.CommitNode, tree *Tree, treePath string, level int) error {
 | 
				
			||||||
 | 
						if level == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entries, err := tree.ListEntries()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entryPaths := make([]string, len(entries))
 | 
				
			||||||
 | 
						entryMap := make(map[string]*TreeEntry)
 | 
				
			||||||
 | 
						for i, entry := range entries {
 | 
				
			||||||
 | 
							entryPaths[i] = entry.Name()
 | 
				
			||||||
 | 
							entryMap[entry.Name()] = entry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commits, err := GetLastCommitForPaths(index, treePath, entryPaths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for entry, cm := range commits {
 | 
				
			||||||
 | 
							if err := c.Put(index.ID().String(), path.Join(treePath, entry), cm.ID().String()); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entryMap[entry].IsDir() {
 | 
				
			||||||
 | 
								subTree, err := tree.SubTree(entry)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := c.recursiveCache(index, subTree, entry, level-1); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										103
									
								
								modules/git/last_commit_cache_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								modules/git/last_commit_cache_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LastCommitCache represents a cache to store last commit
 | 
				
			||||||
 | 
					type LastCommitCache struct {
 | 
				
			||||||
 | 
						repoPath    string
 | 
				
			||||||
 | 
						ttl         int64
 | 
				
			||||||
 | 
						repo        *Repository
 | 
				
			||||||
 | 
						commitCache map[string]*Commit
 | 
				
			||||||
 | 
						cache       Cache
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewLastCommitCache creates a new last commit cache for repo
 | 
				
			||||||
 | 
					func NewLastCommitCache(repoPath string, gitRepo *Repository, ttl int64, cache Cache) *LastCommitCache {
 | 
				
			||||||
 | 
						if cache == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &LastCommitCache{
 | 
				
			||||||
 | 
							repoPath:    repoPath,
 | 
				
			||||||
 | 
							repo:        gitRepo,
 | 
				
			||||||
 | 
							commitCache: make(map[string]*Commit),
 | 
				
			||||||
 | 
							ttl:         ttl,
 | 
				
			||||||
 | 
							cache:       cache,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get get the last commit information by commit id and entry path
 | 
				
			||||||
 | 
					func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) {
 | 
				
			||||||
 | 
						v := c.cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
 | 
				
			||||||
 | 
						if vs, ok := v.(string); ok {
 | 
				
			||||||
 | 
							log("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
 | 
				
			||||||
 | 
							if commit, ok := c.commitCache[vs]; ok {
 | 
				
			||||||
 | 
								log("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
 | 
				
			||||||
 | 
								return commit, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							id, err := c.repo.ConvertToSHA1(vs)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							commit, err := c.repo.getCommit(id)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.commitCache[vs] = commit
 | 
				
			||||||
 | 
							return commit, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CacheCommit will cache the commit from the gitRepository
 | 
				
			||||||
 | 
					func (c *LastCommitCache) CacheCommit(commit *Commit) error {
 | 
				
			||||||
 | 
						return c.recursiveCache(commit, &commit.Tree, "", 1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *LastCommitCache) recursiveCache(commit *Commit, tree *Tree, treePath string, level int) error {
 | 
				
			||||||
 | 
						if level == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entries, err := tree.ListEntries()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entryPaths := make([]string, len(entries))
 | 
				
			||||||
 | 
						entryMap := make(map[string]*TreeEntry)
 | 
				
			||||||
 | 
						for i, entry := range entries {
 | 
				
			||||||
 | 
							entryPaths[i] = entry.Name()
 | 
				
			||||||
 | 
							entryMap[entry.Name()] = entry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commits, err := GetLastCommitForPaths(commit, treePath, entryPaths)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, entryCommit := range commits {
 | 
				
			||||||
 | 
							entry := entryPaths[i]
 | 
				
			||||||
 | 
							if err := c.Put(commit.ID.String(), path.Join(treePath, entryPaths[i]), entryCommit.ID.String()); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if entryMap[entry].IsDir() {
 | 
				
			||||||
 | 
								subTree, err := tree.SubTree(entry)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := c.recursiveCache(commit, subTree, entry, level-1); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,12 +4,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NotesRef is the git ref where Gitea will look for git-notes data.
 | 
					// NotesRef is the git ref where Gitea will look for git-notes data.
 | 
				
			||||||
// The value ("refs/notes/commits") is the default ref used by git-notes.
 | 
					// The value ("refs/notes/commits") is the default ref used by git-notes.
 | 
				
			||||||
const NotesRef = "refs/notes/commits"
 | 
					const NotesRef = "refs/notes/commits"
 | 
				
			||||||
@@ -19,62 +13,3 @@ type Note struct {
 | 
				
			|||||||
	Message []byte
 | 
						Message []byte
 | 
				
			||||||
	Commit  *Commit
 | 
						Commit  *Commit
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetNote retrieves the git-notes data for a given commit.
 | 
					 | 
				
			||||||
func GetNote(repo *Repository, commitID string, note *Note) error {
 | 
					 | 
				
			||||||
	notes, err := repo.GetCommit(NotesRef)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	remainingCommitID := commitID
 | 
					 | 
				
			||||||
	path := ""
 | 
					 | 
				
			||||||
	currentTree := notes.Tree.gogitTree
 | 
					 | 
				
			||||||
	var file *object.File
 | 
					 | 
				
			||||||
	for len(remainingCommitID) > 2 {
 | 
					 | 
				
			||||||
		file, err = currentTree.File(remainingCommitID)
 | 
					 | 
				
			||||||
		if err == nil {
 | 
					 | 
				
			||||||
			path += remainingCommitID
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if err == object.ErrFileNotFound {
 | 
					 | 
				
			||||||
			currentTree, err = currentTree.Tree(remainingCommitID[0:2])
 | 
					 | 
				
			||||||
			path += remainingCommitID[0:2] + "/"
 | 
					 | 
				
			||||||
			remainingCommitID = remainingCommitID[2:]
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	blob := file.Blob
 | 
					 | 
				
			||||||
	dataRc, err := blob.Reader()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	defer dataRc.Close()
 | 
					 | 
				
			||||||
	d, err := ioutil.ReadAll(dataRc)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	note.Message = d
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commitNodeIndex, commitGraphFile := repo.CommitNodeIndex()
 | 
					 | 
				
			||||||
	if commitGraphFile != nil {
 | 
					 | 
				
			||||||
		defer commitGraphFile.Close()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commitNode, err := commitNodeIndex.Get(notes.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	lastCommits, err := GetLastCommitForPaths(commitNode, "", []string{path})
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	note.Commit = convertCommit(lastCommits[path])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										72
									
								
								modules/git/notes_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								modules/git/notes_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetNote retrieves the git-notes data for a given commit.
 | 
				
			||||||
 | 
					func GetNote(repo *Repository, commitID string, note *Note) error {
 | 
				
			||||||
 | 
						notes, err := repo.GetCommit(NotesRef)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						remainingCommitID := commitID
 | 
				
			||||||
 | 
						path := ""
 | 
				
			||||||
 | 
						currentTree := notes.Tree.gogitTree
 | 
				
			||||||
 | 
						var file *object.File
 | 
				
			||||||
 | 
						for len(remainingCommitID) > 2 {
 | 
				
			||||||
 | 
							file, err = currentTree.File(remainingCommitID)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								path += remainingCommitID
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err == object.ErrFileNotFound {
 | 
				
			||||||
 | 
								currentTree, err = currentTree.Tree(remainingCommitID[0:2])
 | 
				
			||||||
 | 
								path += remainingCommitID[0:2] + "/"
 | 
				
			||||||
 | 
								remainingCommitID = remainingCommitID[2:]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						blob := file.Blob
 | 
				
			||||||
 | 
						dataRc, err := blob.Reader()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer dataRc.Close()
 | 
				
			||||||
 | 
						d, err := ioutil.ReadAll(dataRc)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						note.Message = d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitNodeIndex, commitGraphFile := repo.CommitNodeIndex()
 | 
				
			||||||
 | 
						if commitGraphFile != nil {
 | 
				
			||||||
 | 
							defer commitGraphFile.Close()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitNode, err := commitNodeIndex.Get(notes.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastCommits, err := GetLastCommitForPaths(commitNode, "", []string{path})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						note.Commit = convertCommit(lastCommits[path])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								modules/git/notes_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								modules/git/notes_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetNote retrieves the git-notes data for a given commit.
 | 
				
			||||||
 | 
					func GetNote(repo *Repository, commitID string, note *Note) error {
 | 
				
			||||||
 | 
						notes, err := repo.GetCommit(NotesRef)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						path := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tree := ¬es.Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var entry *TreeEntry
 | 
				
			||||||
 | 
						for len(commitID) > 2 {
 | 
				
			||||||
 | 
							entry, err = tree.GetTreeEntryByPath(commitID)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								path += commitID
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if IsErrNotExist(err) {
 | 
				
			||||||
 | 
								tree, err = tree.SubTree(commitID[0:2])
 | 
				
			||||||
 | 
								path += commitID[0:2] + "/"
 | 
				
			||||||
 | 
								commitID = commitID[2:]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dataRc, err := entry.Blob().DataAsync()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer dataRc.Close()
 | 
				
			||||||
 | 
						d, err := ioutil.ReadAll(dataRc)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						note.Message = d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastCommits, err := GetLastCommitForPaths(notes, "", []string{path})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						note.Commit = lastCommits[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,6 +2,8 @@
 | 
				
			|||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
@@ -2,6 +2,8 @@
 | 
				
			|||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
							
								
								
									
										78
									
								
								modules/git/parse_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								modules/git/parse_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ParseTreeEntries parses the output of a `git ls-tree` command.
 | 
				
			||||||
 | 
					func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
 | 
				
			||||||
 | 
						return parseTreeEntries(data, nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
				
			||||||
 | 
						entries := make([]*TreeEntry, 0, 10)
 | 
				
			||||||
 | 
						for pos := 0; pos < len(data); {
 | 
				
			||||||
 | 
							// expect line to be of the form "<mode> <type> <sha>\t<filename>"
 | 
				
			||||||
 | 
							entry := new(TreeEntry)
 | 
				
			||||||
 | 
							entry.ptree = ptree
 | 
				
			||||||
 | 
							if pos+6 > len(data) {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							switch string(data[pos : pos+6]) {
 | 
				
			||||||
 | 
							case "100644":
 | 
				
			||||||
 | 
								entry.entryMode = EntryModeBlob
 | 
				
			||||||
 | 
								pos += 12 // skip over "100644 blob "
 | 
				
			||||||
 | 
							case "100755":
 | 
				
			||||||
 | 
								entry.entryMode = EntryModeExec
 | 
				
			||||||
 | 
								pos += 12 // skip over "100755 blob "
 | 
				
			||||||
 | 
							case "120000":
 | 
				
			||||||
 | 
								entry.entryMode = EntryModeSymlink
 | 
				
			||||||
 | 
								pos += 12 // skip over "120000 blob "
 | 
				
			||||||
 | 
							case "160000":
 | 
				
			||||||
 | 
								entry.entryMode = EntryModeCommit
 | 
				
			||||||
 | 
								pos += 14 // skip over "160000 object "
 | 
				
			||||||
 | 
							case "040000":
 | 
				
			||||||
 | 
								entry.entryMode = EntryModeTree
 | 
				
			||||||
 | 
								pos += 12 // skip over "040000 tree "
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pos+40 > len(data) {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							id, err := NewIDFromString(string(data[pos : pos+40]))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							entry.ID = id
 | 
				
			||||||
 | 
							pos += 41 // skip over sha and trailing space
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							end := pos + bytes.IndexByte(data[pos:], '\n')
 | 
				
			||||||
 | 
							if end < pos {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// In case entry name is surrounded by double quotes(it happens only in git-shell).
 | 
				
			||||||
 | 
							if data[pos] == '"' {
 | 
				
			||||||
 | 
								entry.name, err = strconv.Unquote(string(data[pos:end]))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								entry.name = string(data[pos:end])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pos = end + 1
 | 
				
			||||||
 | 
							entries = append(entries, entry)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return entries, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										159
									
								
								modules/git/pipeline/lfs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								modules/git/pipeline/lfs.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package pipeline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						gogit "github.com/go-git/go-git/v5"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LFSResult represents commits found using a provided pointer file hash
 | 
				
			||||||
 | 
					type LFSResult struct {
 | 
				
			||||||
 | 
						Name           string
 | 
				
			||||||
 | 
						SHA            string
 | 
				
			||||||
 | 
						Summary        string
 | 
				
			||||||
 | 
						When           time.Time
 | 
				
			||||||
 | 
						ParentHashes   []git.SHA1
 | 
				
			||||||
 | 
						BranchName     string
 | 
				
			||||||
 | 
						FullCommitName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type lfsResultSlice []*LFSResult
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a lfsResultSlice) Len() int           { return len(a) }
 | 
				
			||||||
 | 
					func (a lfsResultSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
				
			||||||
 | 
					func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindLFSFile finds commits that contain a provided pointer file hash
 | 
				
			||||||
 | 
					func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
 | 
				
			||||||
 | 
						resultsMap := map[string]*LFSResult{}
 | 
				
			||||||
 | 
						results := make([]*LFSResult, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						basePath := repo.Path
 | 
				
			||||||
 | 
						gogitRepo := repo.GoGitRepo()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitsIter, err := gogitRepo.Log(&gogit.LogOptions{
 | 
				
			||||||
 | 
							Order: gogit.LogOrderCommitterTime,
 | 
				
			||||||
 | 
							All:   true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
 | 
				
			||||||
 | 
							tree, err := gitCommit.Tree()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							treeWalker := object.NewTreeWalker(tree, true, nil)
 | 
				
			||||||
 | 
							defer treeWalker.Close()
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								name, entry, err := treeWalker.Next()
 | 
				
			||||||
 | 
								if err == io.EOF {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if entry.Hash == hash {
 | 
				
			||||||
 | 
									result := LFSResult{
 | 
				
			||||||
 | 
										Name:         name,
 | 
				
			||||||
 | 
										SHA:          gitCommit.Hash.String(),
 | 
				
			||||||
 | 
										Summary:      strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0],
 | 
				
			||||||
 | 
										When:         gitCommit.Author.When,
 | 
				
			||||||
 | 
										ParentHashes: gitCommit.ParentHashes,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									resultsMap[gitCommit.Hash.String()+":"+name] = &result
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil && err != io.EOF {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, result := range resultsMap {
 | 
				
			||||||
 | 
							hasParent := false
 | 
				
			||||||
 | 
							for _, parentHash := range result.ParentHashes {
 | 
				
			||||||
 | 
								if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !hasParent {
 | 
				
			||||||
 | 
								results = append(results, result)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sort.Sort(lfsResultSlice(results))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
 | 
				
			||||||
 | 
						shasToNameReader, shasToNameWriter := io.Pipe()
 | 
				
			||||||
 | 
						nameRevStdinReader, nameRevStdinWriter := io.Pipe()
 | 
				
			||||||
 | 
						errChan := make(chan error, 1)
 | 
				
			||||||
 | 
						wg := sync.WaitGroup{}
 | 
				
			||||||
 | 
						wg.Add(3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer wg.Done()
 | 
				
			||||||
 | 
							scanner := bufio.NewScanner(nameRevStdinReader)
 | 
				
			||||||
 | 
							i := 0
 | 
				
			||||||
 | 
							for scanner.Scan() {
 | 
				
			||||||
 | 
								line := scanner.Text()
 | 
				
			||||||
 | 
								if len(line) == 0 {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								result := results[i]
 | 
				
			||||||
 | 
								result.FullCommitName = line
 | 
				
			||||||
 | 
								result.BranchName = strings.Split(line, "~")[0]
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						go NameRevStdin(shasToNameReader, nameRevStdinWriter, &wg, basePath)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer wg.Done()
 | 
				
			||||||
 | 
							defer shasToNameWriter.Close()
 | 
				
			||||||
 | 
							for _, result := range results {
 | 
				
			||||||
 | 
								i := 0
 | 
				
			||||||
 | 
								if i < len(result.SHA) {
 | 
				
			||||||
 | 
									n, err := shasToNameWriter.Write([]byte(result.SHA)[i:])
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										errChan <- err
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									i += n
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								n := 0
 | 
				
			||||||
 | 
								for n < 1 {
 | 
				
			||||||
 | 
									n, err = shasToNameWriter.Write([]byte{'\n'})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										errChan <- err
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wg.Wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case err, has := <-errChan:
 | 
				
			||||||
 | 
							if has {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return results, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										266
									
								
								modules/git/pipeline/lfs_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								modules/git/pipeline/lfs_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,266 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package pipeline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LFSResult represents commits found using a provided pointer file hash
 | 
				
			||||||
 | 
					type LFSResult struct {
 | 
				
			||||||
 | 
						Name           string
 | 
				
			||||||
 | 
						SHA            string
 | 
				
			||||||
 | 
						Summary        string
 | 
				
			||||||
 | 
						When           time.Time
 | 
				
			||||||
 | 
						ParentHashes   []git.SHA1
 | 
				
			||||||
 | 
						BranchName     string
 | 
				
			||||||
 | 
						FullCommitName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type lfsResultSlice []*LFSResult
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a lfsResultSlice) Len() int           { return len(a) }
 | 
				
			||||||
 | 
					func (a lfsResultSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
				
			||||||
 | 
					func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindLFSFile finds commits that contain a provided pointer file hash
 | 
				
			||||||
 | 
					func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
 | 
				
			||||||
 | 
						resultsMap := map[string]*LFSResult{}
 | 
				
			||||||
 | 
						results := make([]*LFSResult, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						basePath := repo.Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hashStr := hash.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use rev-list to provide us with all commits in order
 | 
				
			||||||
 | 
						revListReader, revListWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = revListWriter.Close()
 | 
				
			||||||
 | 
							_ = revListReader.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderr := strings.Builder{}
 | 
				
			||||||
 | 
							err := git.NewCommand("rev-list", "--all").RunInDirPipeline(repo.Path, revListWriter, &stderr)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = revListWriter.CloseWithError(git.ConcatenateError(err, (&stderr).String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = revListWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
 | 
				
			||||||
 | 
						// so let's create a batch stdin and stdout
 | 
				
			||||||
 | 
						batchStdinReader, batchStdinWriter := io.Pipe()
 | 
				
			||||||
 | 
						batchStdoutReader, batchStdoutWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = batchStdinReader.Close()
 | 
				
			||||||
 | 
							_ = batchStdinWriter.Close()
 | 
				
			||||||
 | 
							_ = batchStdoutReader.Close()
 | 
				
			||||||
 | 
							_ = batchStdoutWriter.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderr := strings.Builder{}
 | 
				
			||||||
 | 
							err := git.NewCommand("cat-file", "--batch").RunInDirFullPipeline(repo.Path, batchStdoutWriter, &stderr, batchStdinReader)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = revListWriter.CloseWithError(git.ConcatenateError(err, (&stderr).String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = revListWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
 | 
				
			||||||
 | 
						batchReader := bufio.NewReader(batchStdoutReader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We'll use a scanner for the revList because it's simpler than a bufio.Reader
 | 
				
			||||||
 | 
						scan := bufio.NewScanner(revListReader)
 | 
				
			||||||
 | 
						trees := [][]byte{}
 | 
				
			||||||
 | 
						paths := []string{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fnameBuf := make([]byte, 4096)
 | 
				
			||||||
 | 
						modeBuf := make([]byte, 40)
 | 
				
			||||||
 | 
						workingShaBuf := make([]byte, 40)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for scan.Scan() {
 | 
				
			||||||
 | 
							// Get the next commit ID
 | 
				
			||||||
 | 
							commitID := scan.Bytes()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// push the commit to the cat-file --batch process
 | 
				
			||||||
 | 
							_, err := batchStdinWriter.Write(commitID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, err = batchStdinWriter.Write([]byte{'\n'})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var curCommit *git.Commit
 | 
				
			||||||
 | 
							curPath := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commitReadingLoop:
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								_, typ, size, err := git.ReadBatchLine(batchReader)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch typ {
 | 
				
			||||||
 | 
								case "tag":
 | 
				
			||||||
 | 
									// This shouldn't happen but if it does well just get the commit and try again
 | 
				
			||||||
 | 
									id, err := git.ReadTagObjectID(batchReader, size)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									_, err = batchStdinWriter.Write([]byte(id + "\n"))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								case "commit":
 | 
				
			||||||
 | 
									// Read in the commit to get its tree and in case this is one of the last used commits
 | 
				
			||||||
 | 
									curCommit, err = git.CommitFromReader(repo, git.MustIDFromString(string(commitID)), io.LimitReader(batchReader, int64(size)))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									_, err := batchStdinWriter.Write([]byte(curCommit.Tree.ID.String() + "\n"))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									curPath = ""
 | 
				
			||||||
 | 
								case "tree":
 | 
				
			||||||
 | 
									var n int64
 | 
				
			||||||
 | 
									for n < size {
 | 
				
			||||||
 | 
										mode, fname, sha, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf)
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return nil, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										n += int64(count)
 | 
				
			||||||
 | 
										if bytes.Equal(sha, []byte(hashStr)) {
 | 
				
			||||||
 | 
											result := LFSResult{
 | 
				
			||||||
 | 
												Name:         curPath + string(fname),
 | 
				
			||||||
 | 
												SHA:          curCommit.ID.String(),
 | 
				
			||||||
 | 
												Summary:      strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
 | 
				
			||||||
 | 
												When:         curCommit.Author.When,
 | 
				
			||||||
 | 
												ParentHashes: curCommit.Parents,
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
 | 
				
			||||||
 | 
										} else if string(mode) == git.EntryModeTree.String() {
 | 
				
			||||||
 | 
											trees = append(trees, sha)
 | 
				
			||||||
 | 
											paths = append(paths, curPath+string(fname)+"/")
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if len(trees) > 0 {
 | 
				
			||||||
 | 
										_, err := batchStdinWriter.Write(trees[len(trees)-1])
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return nil, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										_, err = batchStdinWriter.Write([]byte("\n"))
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return nil, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										curPath = paths[len(paths)-1]
 | 
				
			||||||
 | 
										trees = trees[:len(trees)-1]
 | 
				
			||||||
 | 
										paths = paths[:len(paths)-1]
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										break commitReadingLoop
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := scan.Err(); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, result := range resultsMap {
 | 
				
			||||||
 | 
							hasParent := false
 | 
				
			||||||
 | 
							for _, parentHash := range result.ParentHashes {
 | 
				
			||||||
 | 
								if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !hasParent {
 | 
				
			||||||
 | 
								results = append(results, result)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sort.Sort(lfsResultSlice(results))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
 | 
				
			||||||
 | 
						shasToNameReader, shasToNameWriter := io.Pipe()
 | 
				
			||||||
 | 
						nameRevStdinReader, nameRevStdinWriter := io.Pipe()
 | 
				
			||||||
 | 
						errChan := make(chan error, 1)
 | 
				
			||||||
 | 
						wg := sync.WaitGroup{}
 | 
				
			||||||
 | 
						wg.Add(3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer wg.Done()
 | 
				
			||||||
 | 
							scanner := bufio.NewScanner(nameRevStdinReader)
 | 
				
			||||||
 | 
							i := 0
 | 
				
			||||||
 | 
							for scanner.Scan() {
 | 
				
			||||||
 | 
								line := scanner.Text()
 | 
				
			||||||
 | 
								if len(line) == 0 {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								result := results[i]
 | 
				
			||||||
 | 
								result.FullCommitName = line
 | 
				
			||||||
 | 
								result.BranchName = strings.Split(line, "~")[0]
 | 
				
			||||||
 | 
								i++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						go NameRevStdin(shasToNameReader, nameRevStdinWriter, &wg, basePath)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer wg.Done()
 | 
				
			||||||
 | 
							defer shasToNameWriter.Close()
 | 
				
			||||||
 | 
							for _, result := range results {
 | 
				
			||||||
 | 
								i := 0
 | 
				
			||||||
 | 
								if i < len(result.SHA) {
 | 
				
			||||||
 | 
									n, err := shasToNameWriter.Write([]byte(result.SHA)[i:])
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										errChan <- err
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									i += n
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								var err error
 | 
				
			||||||
 | 
								n := 0
 | 
				
			||||||
 | 
								for n < 1 {
 | 
				
			||||||
 | 
									n, err = shasToNameWriter.Write([]byte{'\n'})
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										errChan <- err
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wg.Wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case err, has := <-errChan:
 | 
				
			||||||
 | 
							if has {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return results, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -9,34 +9,16 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"container/list"
 | 
						"container/list"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gitealog "code.gitea.io/gitea/modules/log"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-billy/v5/osfs"
 | 
					 | 
				
			||||||
	gogit "github.com/go-git/go-git/v5"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/cache"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/storage/filesystem"
 | 
					 | 
				
			||||||
	"github.com/unknwon/com"
 | 
						"github.com/unknwon/com"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Repository represents a Git repository.
 | 
					 | 
				
			||||||
type Repository struct {
 | 
					 | 
				
			||||||
	Path string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tagCache *ObjectCache
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gogitRepo    *gogit.Repository
 | 
					 | 
				
			||||||
	gogitStorage *filesystem.Storage
 | 
					 | 
				
			||||||
	gpgSettings  *GPGSettings
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GPGSettings represents the default GPG settings for this repository
 | 
					// GPGSettings represents the default GPG settings for this repository
 | 
				
			||||||
type GPGSettings struct {
 | 
					type GPGSettings struct {
 | 
				
			||||||
	Sign             bool
 | 
						Sign             bool
 | 
				
			||||||
@@ -93,52 +75,6 @@ func InitRepository(repoPath string, bare bool) error {
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// OpenRepository opens the repository at the given path.
 | 
					 | 
				
			||||||
func OpenRepository(repoPath string) (*Repository, error) {
 | 
					 | 
				
			||||||
	repoPath, err := filepath.Abs(repoPath)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	} else if !isDir(repoPath) {
 | 
					 | 
				
			||||||
		return nil, errors.New("no such file or directory")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fs := osfs.New(repoPath)
 | 
					 | 
				
			||||||
	_, err = fs.Stat(".git")
 | 
					 | 
				
			||||||
	if err == nil {
 | 
					 | 
				
			||||||
		fs, err = fs.Chroot(".git")
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true})
 | 
					 | 
				
			||||||
	gogitRepo, err := gogit.Open(storage, fs)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &Repository{
 | 
					 | 
				
			||||||
		Path:         repoPath,
 | 
					 | 
				
			||||||
		gogitRepo:    gogitRepo,
 | 
					 | 
				
			||||||
		gogitStorage: storage,
 | 
					 | 
				
			||||||
		tagCache:     newObjectCache(),
 | 
					 | 
				
			||||||
	}, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Close this repository, in particular close the underlying gogitStorage if this is not nil
 | 
					 | 
				
			||||||
func (repo *Repository) Close() {
 | 
					 | 
				
			||||||
	if repo == nil || repo.gogitStorage == nil {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := repo.gogitStorage.Close(); err != nil {
 | 
					 | 
				
			||||||
		gitealog.Error("Error closing storage: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GoGitRepo gets the go-git repo representation
 | 
					 | 
				
			||||||
func (repo *Repository) GoGitRepo() *gogit.Repository {
 | 
					 | 
				
			||||||
	return repo.gogitRepo
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsEmpty Check if repository is empty.
 | 
					// IsEmpty Check if repository is empty.
 | 
				
			||||||
func (repo *Repository) IsEmpty() (bool, error) {
 | 
					func (repo *Repository) IsEmpty() (bool, error) {
 | 
				
			||||||
	var errbuf strings.Builder
 | 
						var errbuf strings.Builder
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										76
									
								
								modules/git/repo_base_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								modules/git/repo_base_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitealog "code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"github.com/go-git/go-billy/v5/osfs"
 | 
				
			||||||
 | 
						gogit "github.com/go-git/go-git/v5"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/cache"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/storage/filesystem"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Repository represents a Git repository.
 | 
				
			||||||
 | 
					type Repository struct {
 | 
				
			||||||
 | 
						Path string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tagCache *ObjectCache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gogitRepo    *gogit.Repository
 | 
				
			||||||
 | 
						gogitStorage *filesystem.Storage
 | 
				
			||||||
 | 
						gpgSettings  *GPGSettings
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OpenRepository opens the repository at the given path.
 | 
				
			||||||
 | 
					func OpenRepository(repoPath string) (*Repository, error) {
 | 
				
			||||||
 | 
						repoPath, err := filepath.Abs(repoPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						} else if !isDir(repoPath) {
 | 
				
			||||||
 | 
							return nil, errors.New("no such file or directory")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fs := osfs.New(repoPath)
 | 
				
			||||||
 | 
						_, err = fs.Stat(".git")
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							fs, err = fs.Chroot(".git")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true})
 | 
				
			||||||
 | 
						gogitRepo, err := gogit.Open(storage, fs)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &Repository{
 | 
				
			||||||
 | 
							Path:         repoPath,
 | 
				
			||||||
 | 
							gogitRepo:    gogitRepo,
 | 
				
			||||||
 | 
							gogitStorage: storage,
 | 
				
			||||||
 | 
							tagCache:     newObjectCache(),
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Close this repository, in particular close the underlying gogitStorage if this is not nil
 | 
				
			||||||
 | 
					func (repo *Repository) Close() {
 | 
				
			||||||
 | 
						if repo == nil || repo.gogitStorage == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := repo.gogitStorage.Close(); err != nil {
 | 
				
			||||||
 | 
							gitealog.Error("Error closing storage: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GoGitRepo gets the go-git repo representation
 | 
				
			||||||
 | 
					func (repo *Repository) GoGitRepo() *gogit.Repository {
 | 
				
			||||||
 | 
						return repo.gogitRepo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								modules/git/repo_base_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								modules/git/repo_base_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Repository represents a Git repository.
 | 
				
			||||||
 | 
					type Repository struct {
 | 
				
			||||||
 | 
						Path string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tagCache *ObjectCache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gpgSettings *GPGSettings
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OpenRepository opens the repository at the given path.
 | 
				
			||||||
 | 
					func OpenRepository(repoPath string) (*Repository, error) {
 | 
				
			||||||
 | 
						repoPath, err := filepath.Abs(repoPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						} else if !isDir(repoPath) {
 | 
				
			||||||
 | 
							return nil, errors.New("no such file or directory")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &Repository{
 | 
				
			||||||
 | 
							Path:     repoPath,
 | 
				
			||||||
 | 
							tagCache: newObjectCache(),
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Close this repository, in particular close the underlying gogitStorage if this is not nil
 | 
				
			||||||
 | 
					func (repo *Repository) Close() {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,25 +1,9 @@
 | 
				
			|||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
 | 
					// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (repo *Repository) getBlob(id SHA1) (*Blob, error) {
 | 
					 | 
				
			||||||
	encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, id)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, ErrNotExist{id.String(), ""}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &Blob{
 | 
					 | 
				
			||||||
		ID:              id,
 | 
					 | 
				
			||||||
		gogitEncodedObj: encodedObj,
 | 
					 | 
				
			||||||
	}, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetBlob finds the blob object in the repository.
 | 
					// GetBlob finds the blob object in the repository.
 | 
				
			||||||
func (repo *Repository) GetBlob(idStr string) (*Blob, error) {
 | 
					func (repo *Repository) GetBlob(idStr string) (*Blob, error) {
 | 
				
			||||||
	id, err := NewIDFromString(idStr)
 | 
						id, err := NewIDFromString(idStr)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								modules/git/repo_blob_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								modules/git/repo_blob_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (repo *Repository) getBlob(id SHA1) (*Blob, error) {
 | 
				
			||||||
 | 
						encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, ErrNotExist{id.String(), ""}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &Blob{
 | 
				
			||||||
 | 
							ID:              id,
 | 
				
			||||||
 | 
							gogitEncodedObj: encodedObj,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								modules/git/repo_blob_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								modules/git/repo_blob_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (repo *Repository) getBlob(id SHA1) (*Blob, error) {
 | 
				
			||||||
 | 
						if id.IsZero() {
 | 
				
			||||||
 | 
							return nil, ErrNotExist{id.String(), ""}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &Blob{
 | 
				
			||||||
 | 
							ID:       id,
 | 
				
			||||||
 | 
							repoPath: repo.Path,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,6 @@ package git
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// BranchPrefix base dir of the branch information file store on git
 | 
					// BranchPrefix base dir of the branch information file store on git
 | 
				
			||||||
@@ -26,18 +24,6 @@ func IsBranchExist(repoPath, name string) bool {
 | 
				
			|||||||
	return IsReferenceExist(repoPath, BranchPrefix+name)
 | 
						return IsReferenceExist(repoPath, BranchPrefix+name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsBranchExist returns true if given branch exists in current repository.
 | 
					 | 
				
			||||||
func (repo *Repository) IsBranchExist(name string) bool {
 | 
					 | 
				
			||||||
	if name == "" {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	reference, err := repo.gogitRepo.Reference(plumbing.ReferenceName(BranchPrefix+name), true)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return reference.Type() != plumbing.InvalidReference
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Branch represents a Git branch.
 | 
					// Branch represents a Git branch.
 | 
				
			||||||
type Branch struct {
 | 
					type Branch struct {
 | 
				
			||||||
	Name string
 | 
						Name string
 | 
				
			||||||
@@ -79,25 +65,6 @@ func (repo *Repository) GetDefaultBranch() (string, error) {
 | 
				
			|||||||
	return NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path)
 | 
						return NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBranches returns all branches of the repository.
 | 
					 | 
				
			||||||
func (repo *Repository) GetBranches() ([]string, error) {
 | 
					 | 
				
			||||||
	var branchNames []string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	branches, err := repo.gogitRepo.Branches()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_ = branches.ForEach(func(branch *plumbing.Reference) error {
 | 
					 | 
				
			||||||
		branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix))
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO: Sort?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return branchNames, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetBranch returns a branch by it's name
 | 
					// GetBranch returns a branch by it's name
 | 
				
			||||||
func (repo *Repository) GetBranch(branch string) (*Branch, error) {
 | 
					func (repo *Repository) GetBranch(branch string) (*Branch, error) {
 | 
				
			||||||
	if !repo.IsBranchExist(branch) {
 | 
						if !repo.IsBranchExist(branch) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								modules/git/repo_branch_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								modules/git/repo_branch_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2018 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsBranchExist returns true if given branch exists in current repository.
 | 
				
			||||||
 | 
					func (repo *Repository) IsBranchExist(name string) bool {
 | 
				
			||||||
 | 
						if name == "" {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						reference, err := repo.gogitRepo.Reference(plumbing.ReferenceName(BranchPrefix+name), true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return reference.Type() != plumbing.InvalidReference
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetBranches returns all branches of the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) GetBranches() ([]string, error) {
 | 
				
			||||||
 | 
						var branchNames []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branches, err := repo.gogitRepo.Branches()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ = branches.ForEach(func(branch *plumbing.Reference) error {
 | 
				
			||||||
 | 
							branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix))
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Sort?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return branchNames, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										82
									
								
								modules/git/repo_branch_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								modules/git/repo_branch_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2018 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsBranchExist returns true if given branch exists in current repository.
 | 
				
			||||||
 | 
					func (repo *Repository) IsBranchExist(name string) bool {
 | 
				
			||||||
 | 
						if name == "" {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return IsReferenceExist(repo.Path, BranchPrefix+name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetBranches returns all branches of the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) GetBranches() ([]string, error) {
 | 
				
			||||||
 | 
						return callShowRef(repo.Path, BranchPrefix, "--heads")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func callShowRef(repoPath, prefix, arg string) ([]string, error) {
 | 
				
			||||||
 | 
						var branchNames []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdoutReader, stdoutWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = stdoutReader.Close()
 | 
				
			||||||
 | 
							_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderrBuilder := &strings.Builder{}
 | 
				
			||||||
 | 
							err := NewCommand("show-ref", arg).RunInDirPipeline(repoPath, stdoutWriter, stderrBuilder)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if stderrBuilder.Len() == 0 {
 | 
				
			||||||
 | 
									_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bufReader := bufio.NewReader(stdoutReader)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							// The output of show-ref is simply a list:
 | 
				
			||||||
 | 
							// <sha> SP <ref> LF
 | 
				
			||||||
 | 
							_, err := bufReader.ReadSlice(' ')
 | 
				
			||||||
 | 
							for err == bufio.ErrBufferFull {
 | 
				
			||||||
 | 
								// This shouldn't happen but we'll tolerate it for the sake of peace
 | 
				
			||||||
 | 
								_, err = bufReader.ReadSlice(' ')
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								return branchNames, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							branchName, err := bufReader.ReadString('\n')
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								// This shouldn't happen... but we'll tolerate it for the sake of peace
 | 
				
			||||||
 | 
								return branchNames, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							branchName = strings.TrimPrefix(branchName, prefix)
 | 
				
			||||||
 | 
							if len(branchName) > 0 {
 | 
				
			||||||
 | 
								branchName = branchName[:len(branchName)-1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							branchNames = append(branchNames, branchName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,36 +8,10 @@ package git
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"container/list"
 | 
						"container/list"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
 | 
					 | 
				
			||||||
func (repo *Repository) GetRefCommitID(name string) (string, error) {
 | 
					 | 
				
			||||||
	ref, err := repo.gogitRepo.Reference(plumbing.ReferenceName(name), true)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		if err == plumbing.ErrReferenceNotFound {
 | 
					 | 
				
			||||||
			return "", ErrNotExist{
 | 
					 | 
				
			||||||
				ID: name,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return ref.Hash().String(), nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsCommitExist returns true if given commit exists in current repository.
 | 
					 | 
				
			||||||
func (repo *Repository) IsCommitExist(name string) bool {
 | 
					 | 
				
			||||||
	hash := plumbing.NewHash(name)
 | 
					 | 
				
			||||||
	_, err := repo.gogitRepo.CommitObject(hash)
 | 
					 | 
				
			||||||
	return err == nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetBranchCommitID returns last commit ID string of given branch.
 | 
					// GetBranchCommitID returns last commit ID string of given branch.
 | 
				
			||||||
func (repo *Repository) GetBranchCommitID(name string) (string, error) {
 | 
					func (repo *Repository) GetBranchCommitID(name string) (string, error) {
 | 
				
			||||||
	return repo.GetRefCommitID(BranchPrefix + name)
 | 
						return repo.GetRefCommitID(BranchPrefix + name)
 | 
				
			||||||
@@ -55,78 +29,6 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) {
 | 
				
			|||||||
	return strings.TrimSpace(stdout), nil
 | 
						return strings.TrimSpace(stdout), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature {
 | 
					 | 
				
			||||||
	if t.PGPSignature == "" {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var w strings.Builder
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fmt.Fprintf(&w,
 | 
					 | 
				
			||||||
		"object %s\ntype %s\ntag %s\ntagger ",
 | 
					 | 
				
			||||||
		t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = t.Tagger.Encode(&w); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fmt.Fprintf(&w, "\n\n"); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fmt.Fprintf(&w, t.Message); err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &CommitGPGSignature{
 | 
					 | 
				
			||||||
		Signature: t.PGPSignature,
 | 
					 | 
				
			||||||
		Payload:   strings.TrimSpace(w.String()) + "\n",
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
					 | 
				
			||||||
	var tagObject *object.Tag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gogitCommit, err := repo.gogitRepo.CommitObject(id)
 | 
					 | 
				
			||||||
	if err == plumbing.ErrObjectNotFound {
 | 
					 | 
				
			||||||
		tagObject, err = repo.gogitRepo.TagObject(id)
 | 
					 | 
				
			||||||
		if err == plumbing.ErrObjectNotFound {
 | 
					 | 
				
			||||||
			return nil, ErrNotExist{
 | 
					 | 
				
			||||||
				ID: id.String(),
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if err == nil {
 | 
					 | 
				
			||||||
			gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		// if we get a plumbing.ErrObjectNotFound here then the repository is broken and it should be 500
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commit := convertCommit(gogitCommit)
 | 
					 | 
				
			||||||
	commit.repo = repo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if tagObject != nil {
 | 
					 | 
				
			||||||
		commit.CommitMessage = strings.TrimSpace(tagObject.Message)
 | 
					 | 
				
			||||||
		commit.Author = &tagObject.Tagger
 | 
					 | 
				
			||||||
		commit.Signature = convertPGPSignatureForTag(tagObject)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tree, err := gogitCommit.Tree()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commit.Tree.ID = tree.Hash
 | 
					 | 
				
			||||||
	commit.Tree.gogitTree = tree
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return commit, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ConvertToSHA1 returns a Hash object from a potential ID string
 | 
					// ConvertToSHA1 returns a Hash object from a potential ID string
 | 
				
			||||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
 | 
					func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
 | 
				
			||||||
	if len(commitID) != 40 {
 | 
						if len(commitID) != 40 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										110
									
								
								modules/git/repo_commit_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								modules/git/repo_commit_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
 | 
				
			||||||
 | 
					func (repo *Repository) GetRefCommitID(name string) (string, error) {
 | 
				
			||||||
 | 
						ref, err := repo.gogitRepo.Reference(plumbing.ReferenceName(name), true)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if err == plumbing.ErrReferenceNotFound {
 | 
				
			||||||
 | 
								return "", ErrNotExist{
 | 
				
			||||||
 | 
									ID: name,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ref.Hash().String(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsCommitExist returns true if given commit exists in current repository.
 | 
				
			||||||
 | 
					func (repo *Repository) IsCommitExist(name string) bool {
 | 
				
			||||||
 | 
						hash := plumbing.NewHash(name)
 | 
				
			||||||
 | 
						_, err := repo.gogitRepo.CommitObject(hash)
 | 
				
			||||||
 | 
						return err == nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature {
 | 
				
			||||||
 | 
						if t.PGPSignature == "" {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var w strings.Builder
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprintf(&w,
 | 
				
			||||||
 | 
							"object %s\ntype %s\ntag %s\ntagger ",
 | 
				
			||||||
 | 
							t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = t.Tagger.Encode(&w); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprintf(&w, "\n\n"); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fmt.Fprintf(&w, t.Message); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &CommitGPGSignature{
 | 
				
			||||||
 | 
							Signature: t.PGPSignature,
 | 
				
			||||||
 | 
							Payload:   strings.TrimSpace(w.String()) + "\n",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
				
			||||||
 | 
						var tagObject *object.Tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gogitCommit, err := repo.gogitRepo.CommitObject(id)
 | 
				
			||||||
 | 
						if err == plumbing.ErrObjectNotFound {
 | 
				
			||||||
 | 
							tagObject, err = repo.gogitRepo.TagObject(id)
 | 
				
			||||||
 | 
							if err == plumbing.ErrObjectNotFound {
 | 
				
			||||||
 | 
								return nil, ErrNotExist{
 | 
				
			||||||
 | 
									ID: id.String(),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// if we get a plumbing.ErrObjectNotFound here then the repository is broken and it should be 500
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit := convertCommit(gogitCommit)
 | 
				
			||||||
 | 
						commit.repo = repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if tagObject != nil {
 | 
				
			||||||
 | 
							commit.CommitMessage = strings.TrimSpace(tagObject.Message)
 | 
				
			||||||
 | 
							commit.Author = &tagObject.Tagger
 | 
				
			||||||
 | 
							commit.Signature = convertPGPSignatureForTag(tagObject)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tree, err := gogitCommit.Tree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit.Tree.ID = tree.Hash
 | 
				
			||||||
 | 
						commit.Tree.gogitTree = tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return commit, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										109
									
								
								modules/git/repo_commit_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								modules/git/repo_commit_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResolveReference resolves a name to a reference
 | 
				
			||||||
 | 
					func (repo *Repository) ResolveReference(name string) (string, error) {
 | 
				
			||||||
 | 
						stdout, err := NewCommand("show-ref", "--hash", name).RunInDir(repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if strings.Contains(err.Error(), "not a valid ref") {
 | 
				
			||||||
 | 
								return "", ErrNotExist{name, ""}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						stdout = strings.TrimSpace(stdout)
 | 
				
			||||||
 | 
						if stdout == "" {
 | 
				
			||||||
 | 
							return "", ErrNotExist{name, ""}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return stdout, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
 | 
				
			||||||
 | 
					func (repo *Repository) GetRefCommitID(name string) (string, error) {
 | 
				
			||||||
 | 
						stdout, err := NewCommand("show-ref", "--verify", "--hash", name).RunInDir(repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if strings.Contains(err.Error(), "not a valid ref") {
 | 
				
			||||||
 | 
								return "", ErrNotExist{name, ""}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return strings.TrimSpace(stdout), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsCommitExist returns true if given commit exists in current repository.
 | 
				
			||||||
 | 
					func (repo *Repository) IsCommitExist(name string) bool {
 | 
				
			||||||
 | 
						_, err := NewCommand("cat-file", "-e", name).RunInDir(repo.Path)
 | 
				
			||||||
 | 
						return err == nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
				
			||||||
 | 
						stdoutReader, stdoutWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = stdoutReader.Close()
 | 
				
			||||||
 | 
							_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderr := strings.Builder{}
 | 
				
			||||||
 | 
							err := NewCommand("cat-file", "--batch").RunInDirFullPipeline(repo.Path, stdoutWriter, &stderr, strings.NewReader(id.String()+"\n"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bufReader := bufio.NewReader(stdoutReader)
 | 
				
			||||||
 | 
						_, typ, size, err := ReadBatchLine(bufReader)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch typ {
 | 
				
			||||||
 | 
						case "tag":
 | 
				
			||||||
 | 
							// then we need to parse the tag
 | 
				
			||||||
 | 
							// and load the commit
 | 
				
			||||||
 | 
							data, err := ioutil.ReadAll(io.LimitReader(bufReader, size))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							tag, err := parseTagData(data)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							tag.repo = repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							commit, err := tag.Commit()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							commit.CommitMessage = strings.TrimSpace(tag.Message)
 | 
				
			||||||
 | 
							commit.Author = tag.Tagger
 | 
				
			||||||
 | 
							commit.Signature = tag.Signature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return commit, nil
 | 
				
			||||||
 | 
						case "commit":
 | 
				
			||||||
 | 
							return CommitFromReader(repo, id, io.LimitReader(bufReader, size))
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							_ = stdoutReader.CloseWithError(fmt.Errorf("unknown typ: %s", typ))
 | 
				
			||||||
 | 
							log("Unknown typ: %s", typ)
 | 
				
			||||||
 | 
							return nil, ErrNotExist{
 | 
				
			||||||
 | 
								ID: id.String(),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,6 +3,8 @@
 | 
				
			|||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
@@ -4,111 +4,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/analyze"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-enry/go-enry/v2"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fileSizeLimit int64 = 16 * 1024 // 16 KiB
 | 
					const fileSizeLimit int64 = 16 * 1024 // 16 KiB
 | 
				
			||||||
const bigFileSize int64 = 1024 * 1024 // 1 MiB
 | 
					const bigFileSize int64 = 1024 * 1024 // 1 MiB
 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetLanguageStats calculates language stats for git repository at specified commit
 | 
					 | 
				
			||||||
func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
 | 
					 | 
				
			||||||
	r, err := git.PlainOpen(repo.Path)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rev, err := r.ResolveRevision(plumbing.Revision(commitID))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commit, err := r.CommitObject(*rev)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tree, err := commit.Tree()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sizes := make(map[string]int64)
 | 
					 | 
				
			||||||
	err = tree.Files().ForEach(func(f *object.File) error {
 | 
					 | 
				
			||||||
		if f.Size == 0 || enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) ||
 | 
					 | 
				
			||||||
			enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// If content can not be read or file is too big just do detection by filename
 | 
					 | 
				
			||||||
		var content []byte
 | 
					 | 
				
			||||||
		if f.Size <= bigFileSize {
 | 
					 | 
				
			||||||
			content, _ = readFile(f, fileSizeLimit)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if enry.IsGenerated(f.Name, content) {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// TODO: Use .gitattributes file for linguist overrides
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		language := analyze.GetCodeLanguage(f.Name, content)
 | 
					 | 
				
			||||||
		if language == enry.OtherLanguage || language == "" {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// group languages, such as Pug -> HTML; SCSS -> CSS
 | 
					 | 
				
			||||||
		group := enry.GetLanguageGroup(language)
 | 
					 | 
				
			||||||
		if group != "" {
 | 
					 | 
				
			||||||
			language = group
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		sizes[language] += f.Size
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// filter special languages unless they are the only language
 | 
					 | 
				
			||||||
	if len(sizes) > 1 {
 | 
					 | 
				
			||||||
		for language := range sizes {
 | 
					 | 
				
			||||||
			langtype := enry.GetLanguageType(language)
 | 
					 | 
				
			||||||
			if langtype != enry.Programming && langtype != enry.Markup {
 | 
					 | 
				
			||||||
				delete(sizes, language)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return sizes, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func readFile(f *object.File, limit int64) ([]byte, error) {
 | 
					 | 
				
			||||||
	r, err := f.Reader()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer r.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if limit <= 0 {
 | 
					 | 
				
			||||||
		return ioutil.ReadAll(r)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	size := f.Size
 | 
					 | 
				
			||||||
	if limit > 0 && size > limit {
 | 
					 | 
				
			||||||
		size = limit
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	buf := bytes.NewBuffer(nil)
 | 
					 | 
				
			||||||
	buf.Grow(int(size))
 | 
					 | 
				
			||||||
	_, err = io.Copy(buf, io.LimitReader(r, limit))
 | 
					 | 
				
			||||||
	return buf.Bytes(), err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										113
									
								
								modules/git/repo_language_stats_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								modules/git/repo_language_stats_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/analyze"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-enry/go-enry/v2"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLanguageStats calculates language stats for git repository at specified commit
 | 
				
			||||||
 | 
					func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
 | 
				
			||||||
 | 
						r, err := git.PlainOpen(repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rev, err := r.ResolveRevision(plumbing.Revision(commitID))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commit, err := r.CommitObject(*rev)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tree, err := commit.Tree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sizes := make(map[string]int64)
 | 
				
			||||||
 | 
						err = tree.Files().ForEach(func(f *object.File) error {
 | 
				
			||||||
 | 
							if f.Size == 0 || enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) ||
 | 
				
			||||||
 | 
								enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If content can not be read or file is too big just do detection by filename
 | 
				
			||||||
 | 
							var content []byte
 | 
				
			||||||
 | 
							if f.Size <= bigFileSize {
 | 
				
			||||||
 | 
								content, _ = readFile(f, fileSizeLimit)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if enry.IsGenerated(f.Name, content) {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: Use .gitattributes file for linguist overrides
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							language := analyze.GetCodeLanguage(f.Name, content)
 | 
				
			||||||
 | 
							if language == enry.OtherLanguage || language == "" {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// group languages, such as Pug -> HTML; SCSS -> CSS
 | 
				
			||||||
 | 
							group := enry.GetLanguageGroup(language)
 | 
				
			||||||
 | 
							if group != "" {
 | 
				
			||||||
 | 
								language = group
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sizes[language] += f.Size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// filter special languages unless they are the only language
 | 
				
			||||||
 | 
						if len(sizes) > 1 {
 | 
				
			||||||
 | 
							for language := range sizes {
 | 
				
			||||||
 | 
								langtype := enry.GetLanguageType(language)
 | 
				
			||||||
 | 
								if langtype != enry.Programming && langtype != enry.Markup {
 | 
				
			||||||
 | 
									delete(sizes, language)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sizes, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readFile(f *object.File, limit int64) ([]byte, error) {
 | 
				
			||||||
 | 
						r, err := f.Reader()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if limit <= 0 {
 | 
				
			||||||
 | 
							return ioutil.ReadAll(r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size := f.Size
 | 
				
			||||||
 | 
						if limit > 0 && size > limit {
 | 
				
			||||||
 | 
							size = limit
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						buf := bytes.NewBuffer(nil)
 | 
				
			||||||
 | 
						buf.Grow(int(size))
 | 
				
			||||||
 | 
						_, err = io.Copy(buf, io.LimitReader(r, limit))
 | 
				
			||||||
 | 
						return buf.Bytes(), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										109
									
								
								modules/git/repo_language_stats_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								modules/git/repo_language_stats_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/analyze"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-enry/go-enry/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLanguageStats calculates language stats for git repository at specified commit
 | 
				
			||||||
 | 
					func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
 | 
				
			||||||
 | 
						// FIXME: We can be more efficient here...
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// We're expecting that we will be reading a lot of blobs and the trees
 | 
				
			||||||
 | 
						// Thus we should use a shared `cat-file --batch` to get all of this data
 | 
				
			||||||
 | 
						// And keep the buffers around with resets as necessary.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// It's more complicated so...
 | 
				
			||||||
 | 
						commit, err := repo.GetCommit(commitID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log("Unable to get commit for: %s", commitID)
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tree := commit.Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entries, err := tree.ListEntriesRecursive()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sizes := make(map[string]int64)
 | 
				
			||||||
 | 
						for _, f := range entries {
 | 
				
			||||||
 | 
							if f.Size() == 0 || enry.IsVendor(f.Name()) || enry.IsDotFile(f.Name()) ||
 | 
				
			||||||
 | 
								enry.IsDocumentation(f.Name()) || enry.IsConfiguration(f.Name()) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If content can not be read or file is too big just do detection by filename
 | 
				
			||||||
 | 
							var content []byte
 | 
				
			||||||
 | 
							if f.Size() <= bigFileSize {
 | 
				
			||||||
 | 
								content, _ = readFile(f, fileSizeLimit)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if enry.IsGenerated(f.Name(), content) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: Use .gitattributes file for linguist overrides
 | 
				
			||||||
 | 
							// FIXME: Why can't we split this and the IsGenerated tests to avoid reading the blob unless absolutely necessary?
 | 
				
			||||||
 | 
							// - eg. do the all the detection tests using filename first before reading content.
 | 
				
			||||||
 | 
							language := analyze.GetCodeLanguage(f.Name(), content)
 | 
				
			||||||
 | 
							if language == enry.OtherLanguage || language == "" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// group languages, such as Pug -> HTML; SCSS -> CSS
 | 
				
			||||||
 | 
							group := enry.GetLanguageGroup(language)
 | 
				
			||||||
 | 
							if group != "" {
 | 
				
			||||||
 | 
								language = group
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sizes[language] += f.Size()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							continue
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// filter special languages unless they are the only language
 | 
				
			||||||
 | 
						if len(sizes) > 1 {
 | 
				
			||||||
 | 
							for language := range sizes {
 | 
				
			||||||
 | 
								langtype := enry.GetLanguageType(language)
 | 
				
			||||||
 | 
								if langtype != enry.Programming && langtype != enry.Markup {
 | 
				
			||||||
 | 
									delete(sizes, language)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sizes, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readFile(entry *TreeEntry, limit int64) ([]byte, error) {
 | 
				
			||||||
 | 
						// FIXME: We can probably be a little more efficient here... see above
 | 
				
			||||||
 | 
						r, err := entry.Blob().DataAsync()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if limit <= 0 {
 | 
				
			||||||
 | 
							return ioutil.ReadAll(r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size := entry.Size()
 | 
				
			||||||
 | 
						if limit > 0 && size > limit {
 | 
				
			||||||
 | 
							size = limit
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						buf := bytes.NewBuffer(nil)
 | 
				
			||||||
 | 
						buf.Grow(int(size))
 | 
				
			||||||
 | 
						_, err = io.Copy(buf, io.LimitReader(r, limit))
 | 
				
			||||||
 | 
						return buf.Bytes(), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -27,6 +27,11 @@ const (
 | 
				
			|||||||
	ObjectBranch ObjectType = "branch"
 | 
						ObjectBranch ObjectType = "branch"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Bytes returns the byte array for the Object Type
 | 
				
			||||||
 | 
					func (o ObjectType) Bytes() []byte {
 | 
				
			||||||
 | 
						return []byte(o)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HashObject takes a reader and returns SHA1 hash for that reader
 | 
					// HashObject takes a reader and returns SHA1 hash for that reader
 | 
				
			||||||
func (repo *Repository) HashObject(reader io.Reader) (SHA1, error) {
 | 
					func (repo *Repository) HashObject(reader io.Reader) (SHA1, error) {
 | 
				
			||||||
	idStr, err := repo.hashObject(reader)
 | 
						idStr, err := repo.hashObject(reader)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,52 +4,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetRefs returns all references of the repository.
 | 
					// GetRefs returns all references of the repository.
 | 
				
			||||||
func (repo *Repository) GetRefs() ([]*Reference, error) {
 | 
					func (repo *Repository) GetRefs() ([]*Reference, error) {
 | 
				
			||||||
	return repo.GetRefsFiltered("")
 | 
						return repo.GetRefsFiltered("")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with.
 | 
					 | 
				
			||||||
func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
 | 
					 | 
				
			||||||
	r, err := git.PlainOpen(repo.Path)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	refsIter, err := r.References()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	refs := make([]*Reference, 0)
 | 
					 | 
				
			||||||
	if err = refsIter.ForEach(func(ref *plumbing.Reference) error {
 | 
					 | 
				
			||||||
		if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() &&
 | 
					 | 
				
			||||||
			(pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) {
 | 
					 | 
				
			||||||
			refType := string(ObjectCommit)
 | 
					 | 
				
			||||||
			if ref.Name().IsTag() {
 | 
					 | 
				
			||||||
				// tags can be of type `commit` (lightweight) or `tag` (annotated)
 | 
					 | 
				
			||||||
				if tagType, _ := repo.GetTagType(ref.Hash()); err == nil {
 | 
					 | 
				
			||||||
					refType = tagType
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			r := &Reference{
 | 
					 | 
				
			||||||
				Name:   ref.Name().String(),
 | 
					 | 
				
			||||||
				Object: ref.Hash(),
 | 
					 | 
				
			||||||
				Type:   refType,
 | 
					 | 
				
			||||||
				repo:   repo,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			refs = append(refs, r)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return refs, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								modules/git/repo_ref_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								modules/git/repo_ref_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with.
 | 
				
			||||||
 | 
					func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
 | 
				
			||||||
 | 
						r, err := git.PlainOpen(repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						refsIter, err := r.References()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						refs := make([]*Reference, 0)
 | 
				
			||||||
 | 
						if err = refsIter.ForEach(func(ref *plumbing.Reference) error {
 | 
				
			||||||
 | 
							if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() &&
 | 
				
			||||||
 | 
								(pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) {
 | 
				
			||||||
 | 
								refType := string(ObjectCommit)
 | 
				
			||||||
 | 
								if ref.Name().IsTag() {
 | 
				
			||||||
 | 
									// tags can be of type `commit` (lightweight) or `tag` (annotated)
 | 
				
			||||||
 | 
									if tagType, _ := repo.GetTagType(ref.Hash()); err == nil {
 | 
				
			||||||
 | 
										refType = tagType
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								r := &Reference{
 | 
				
			||||||
 | 
									Name:   ref.Name().String(),
 | 
				
			||||||
 | 
									Object: ref.Hash(),
 | 
				
			||||||
 | 
									Type:   refType,
 | 
				
			||||||
 | 
									repo:   repo,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								refs = append(refs, r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return refs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										84
									
								
								modules/git/repo_ref_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								modules/git/repo_ref_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRefsFiltered returns all references of the repository that matches patterm exactly or starting with.
 | 
				
			||||||
 | 
					func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
 | 
				
			||||||
 | 
						stdoutReader, stdoutWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = stdoutReader.Close()
 | 
				
			||||||
 | 
							_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderrBuilder := &strings.Builder{}
 | 
				
			||||||
 | 
							err := NewCommand("for-each-ref").RunInDirPipeline(repo.Path, stdoutWriter, stderrBuilder)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						refs := make([]*Reference, 0)
 | 
				
			||||||
 | 
						bufReader := bufio.NewReader(stdoutReader)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							// The output of for-each-ref is simply a list:
 | 
				
			||||||
 | 
							// <sha> SP <type> TAB <ref> LF
 | 
				
			||||||
 | 
							sha, err := bufReader.ReadString(' ')
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							sha = sha[:len(sha)-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							typ, err := bufReader.ReadString('\t')
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								// This should not happen, but we'll tolerate it
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							typ = typ[:len(typ)-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							refName, err := bufReader.ReadString('\n')
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								// This should not happen, but we'll tolerate it
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							refName = refName[:len(refName)-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// refName cannot be HEAD but can be remotes or stash
 | 
				
			||||||
 | 
							if strings.HasPrefix(refName, "/refs/remotes/") || refName == "/refs/stash" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pattern == "" || strings.HasPrefix(refName, pattern) {
 | 
				
			||||||
 | 
								r := &Reference{
 | 
				
			||||||
 | 
									Name:   refName,
 | 
				
			||||||
 | 
									Object: MustIDFromString(sha),
 | 
				
			||||||
 | 
									Type:   typ,
 | 
				
			||||||
 | 
									repo:   repo,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								refs = append(refs, r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return refs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,8 +8,6 @@ package git
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TagPrefix tags prefix path on the repository
 | 
					// TagPrefix tags prefix path on the repository
 | 
				
			||||||
@@ -20,12 +18,6 @@ func IsTagExist(repoPath, name string) bool {
 | 
				
			|||||||
	return IsReferenceExist(repoPath, TagPrefix+name)
 | 
						return IsReferenceExist(repoPath, TagPrefix+name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsTagExist returns true if given tag exists in the repository.
 | 
					 | 
				
			||||||
func (repo *Repository) IsTagExist(name string) bool {
 | 
					 | 
				
			||||||
	_, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true)
 | 
					 | 
				
			||||||
	return err == nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// CreateTag create one tag in the repository
 | 
					// CreateTag create one tag in the repository
 | 
				
			||||||
func (repo *Repository) CreateTag(name, revision string) error {
 | 
					func (repo *Repository) CreateTag(name, revision string) error {
 | 
				
			||||||
	_, err := NewCommand("tag", "--", name, revision).RunInDir(repo.Path)
 | 
						_, err := NewCommand("tag", "--", name, revision).RunInDir(repo.Path)
 | 
				
			||||||
@@ -224,29 +216,6 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, error) {
 | 
				
			|||||||
	return tags, nil
 | 
						return tags, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetTags returns all tags of the repository.
 | 
					 | 
				
			||||||
func (repo *Repository) GetTags() ([]string, error) {
 | 
					 | 
				
			||||||
	var tagNames []string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tags, err := repo.gogitRepo.Tags()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_ = tags.ForEach(func(tag *plumbing.Reference) error {
 | 
					 | 
				
			||||||
		tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Reverse order
 | 
					 | 
				
			||||||
	for i := 0; i < len(tagNames)/2; i++ {
 | 
					 | 
				
			||||||
		j := len(tagNames) - i - 1
 | 
					 | 
				
			||||||
		tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return tagNames, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
 | 
					// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
 | 
				
			||||||
func (repo *Repository) GetTagType(id SHA1) (string, error) {
 | 
					func (repo *Repository) GetTagType(id SHA1) (string, error) {
 | 
				
			||||||
	// Get tag type
 | 
						// Get tag type
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								modules/git/repo_tag_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								modules/git/repo_tag_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsTagExist returns true if given tag exists in the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) IsTagExist(name string) bool {
 | 
				
			||||||
 | 
						_, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true)
 | 
				
			||||||
 | 
						return err == nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTags returns all tags of the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) GetTags() ([]string, error) {
 | 
				
			||||||
 | 
						var tagNames []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags, err := repo.gogitRepo.Tags()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ = tags.ForEach(func(tag *plumbing.Reference) error {
 | 
				
			||||||
 | 
							tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reverse order
 | 
				
			||||||
 | 
						for i := 0; i < len(tagNames)/2; i++ {
 | 
				
			||||||
 | 
							j := len(tagNames) - i - 1
 | 
				
			||||||
 | 
							tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return tagNames, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								modules/git/repo_tag_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								modules/git/repo_tag_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsTagExist returns true if given tag exists in the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) IsTagExist(name string) bool {
 | 
				
			||||||
 | 
						return IsReferenceExist(repo.Path, TagPrefix+name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTags returns all tags of the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) GetTags() ([]string, error) {
 | 
				
			||||||
 | 
						return callShowRef(repo.Path, TagPrefix, "--tags")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -13,45 +13,6 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (repo *Repository) getTree(id SHA1) (*Tree, error) {
 | 
					 | 
				
			||||||
	gogitTree, err := repo.gogitRepo.TreeObject(id)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tree := NewTree(repo, id)
 | 
					 | 
				
			||||||
	tree.gogitTree = gogitTree
 | 
					 | 
				
			||||||
	return tree, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetTree find the tree object in the repository.
 | 
					 | 
				
			||||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
 | 
					 | 
				
			||||||
	if len(idStr) != 40 {
 | 
					 | 
				
			||||||
		res, err := NewCommand("rev-parse", "--verify", idStr).RunInDir(repo.Path)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if len(res) > 0 {
 | 
					 | 
				
			||||||
			idStr = res[:len(res)-1]
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	id, err := NewIDFromString(idStr)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	resolvedID := id
 | 
					 | 
				
			||||||
	commitObject, err := repo.gogitRepo.CommitObject(id)
 | 
					 | 
				
			||||||
	if err == nil {
 | 
					 | 
				
			||||||
		id = SHA1(commitObject.TreeHash)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	treeObject, err := repo.getTree(id)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	treeObject.ResolvedID = resolvedID
 | 
					 | 
				
			||||||
	return treeObject, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// CommitTreeOpts represents the possible options to CommitTree
 | 
					// CommitTreeOpts represents the possible options to CommitTree
 | 
				
			||||||
type CommitTreeOpts struct {
 | 
					type CommitTreeOpts struct {
 | 
				
			||||||
	Parents    []string
 | 
						Parents    []string
 | 
				
			||||||
@@ -102,7 +63,7 @@ func (repo *Repository) CommitTree(author *Signature, committer *Signature, tree
 | 
				
			|||||||
	err = cmd.RunInDirTimeoutEnvFullPipeline(env, -1, repo.Path, stdout, stderr, messageBytes)
 | 
						err = cmd.RunInDirTimeoutEnvFullPipeline(env, -1, repo.Path, stdout, stderr, messageBytes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return SHA1{}, concatenateError(err, stderr.String())
 | 
							return SHA1{}, ConcatenateError(err, stderr.String())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return NewIDFromString(strings.TrimSpace(stdout.String()))
 | 
						return NewIDFromString(strings.TrimSpace(stdout.String()))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										47
									
								
								modules/git/repo_tree_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								modules/git/repo_tree_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (repo *Repository) getTree(id SHA1) (*Tree, error) {
 | 
				
			||||||
 | 
						gogitTree, err := repo.gogitRepo.TreeObject(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tree := NewTree(repo, id)
 | 
				
			||||||
 | 
						tree.gogitTree = gogitTree
 | 
				
			||||||
 | 
						return tree, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTree find the tree object in the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) GetTree(idStr string) (*Tree, error) {
 | 
				
			||||||
 | 
						if len(idStr) != 40 {
 | 
				
			||||||
 | 
							res, err := NewCommand("rev-parse", "--verify", idStr).RunInDir(repo.Path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(res) > 0 {
 | 
				
			||||||
 | 
								idStr = res[:len(res)-1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, err := NewIDFromString(idStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resolvedID := id
 | 
				
			||||||
 | 
						commitObject, err := repo.gogitRepo.CommitObject(id)
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							id = SHA1(commitObject.TreeHash)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						treeObject, err := repo.getTree(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						treeObject.ResolvedID = resolvedID
 | 
				
			||||||
 | 
						return treeObject, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								modules/git/repo_tree_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								modules/git/repo_tree_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (repo *Repository) getTree(id SHA1) (*Tree, error) {
 | 
				
			||||||
 | 
						stdoutReader, stdoutWriter := io.Pipe()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							_ = stdoutReader.Close()
 | 
				
			||||||
 | 
							_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							stderr := &strings.Builder{}
 | 
				
			||||||
 | 
							err := NewCommand("cat-file", "--batch").RunInDirFullPipeline(repo.Path, stdoutWriter, stderr, strings.NewReader(id.String()+"\n"))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String()))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_ = stdoutWriter.Close()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bufReader := bufio.NewReader(stdoutReader)
 | 
				
			||||||
 | 
						// ignore the SHA
 | 
				
			||||||
 | 
						_, typ, _, err := ReadBatchLine(bufReader)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch typ {
 | 
				
			||||||
 | 
						case "tag":
 | 
				
			||||||
 | 
							resolvedID := id
 | 
				
			||||||
 | 
							data, err := ioutil.ReadAll(bufReader)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							tag, err := parseTagData(data)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							commit, err := tag.Commit()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							commit.Tree.ResolvedID = resolvedID
 | 
				
			||||||
 | 
							log("tag.commit.Tree: %s %v", commit.Tree.ID.String(), commit.Tree.repo)
 | 
				
			||||||
 | 
							return &commit.Tree, nil
 | 
				
			||||||
 | 
						case "commit":
 | 
				
			||||||
 | 
							commit, err := CommitFromReader(repo, id, bufReader)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								_ = stdoutReader.CloseWithError(err)
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							commit.Tree.ResolvedID = commit.ID
 | 
				
			||||||
 | 
							log("commit.Tree: %s %v", commit.Tree.ID.String(), commit.Tree.repo)
 | 
				
			||||||
 | 
							return &commit.Tree, nil
 | 
				
			||||||
 | 
						case "tree":
 | 
				
			||||||
 | 
							stdoutReader.Close()
 | 
				
			||||||
 | 
							tree := NewTree(repo, id)
 | 
				
			||||||
 | 
							tree.ResolvedID = id
 | 
				
			||||||
 | 
							return tree, nil
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							_ = stdoutReader.CloseWithError(fmt.Errorf("unknown typ: %s", typ))
 | 
				
			||||||
 | 
							return nil, ErrNotExist{
 | 
				
			||||||
 | 
								ID: id.String(),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTree find the tree object in the repository.
 | 
				
			||||||
 | 
					func (repo *Repository) GetTree(idStr string) (*Tree, error) {
 | 
				
			||||||
 | 
						if len(idStr) != 40 {
 | 
				
			||||||
 | 
							res, err := NewCommand("rev-parse", "--verify", idStr).RunInDir(repo.Path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(res) > 0 {
 | 
				
			||||||
 | 
								idStr = res[:len(res)-1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, err := NewIDFromString(idStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return repo.getTree(id)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,8 +10,6 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EmptySHA defines empty git SHA
 | 
					// EmptySHA defines empty git SHA
 | 
				
			||||||
@@ -23,9 +21,6 @@ const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
 | 
				
			|||||||
// SHAPattern can be used to determine if a string is an valid sha
 | 
					// SHAPattern can be used to determine if a string is an valid sha
 | 
				
			||||||
var SHAPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
 | 
					var SHAPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SHA1 a git commit name
 | 
					 | 
				
			||||||
type SHA1 = plumbing.Hash
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
 | 
					// MustID always creates a new SHA1 from a [20]byte array with no validation of input.
 | 
				
			||||||
func MustID(b []byte) SHA1 {
 | 
					func MustID(b []byte) SHA1 {
 | 
				
			||||||
	var id SHA1
 | 
						var id SHA1
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								modules/git/sha1_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								modules/git/sha1_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SHA1 a git commit name
 | 
				
			||||||
 | 
					type SHA1 = plumbing.Hash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ComputeBlobHash compute the hash for a given blob content
 | 
				
			||||||
 | 
					func ComputeBlobHash(content []byte) SHA1 {
 | 
				
			||||||
 | 
						return plumbing.ComputeHash(plumbing.BlobObject, content)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								modules/git/sha1_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								modules/git/sha1_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/sha1"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
 | 
						"hash"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SHA1 a git commit name
 | 
				
			||||||
 | 
					type SHA1 [20]byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns a string representation of the SHA
 | 
				
			||||||
 | 
					func (s SHA1) String() string {
 | 
				
			||||||
 | 
						return hex.EncodeToString(s[:])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsZero returns whether this SHA1 is all zeroes
 | 
				
			||||||
 | 
					func (s SHA1) IsZero() bool {
 | 
				
			||||||
 | 
						var empty SHA1
 | 
				
			||||||
 | 
						return s == empty
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ComputeBlobHash compute the hash for a given blob content
 | 
				
			||||||
 | 
					func ComputeBlobHash(content []byte) SHA1 {
 | 
				
			||||||
 | 
						return ComputeHash(ObjectBlob, content)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ComputeHash compute the hash for a given ObjectType and content
 | 
				
			||||||
 | 
					func ComputeHash(t ObjectType, content []byte) SHA1 {
 | 
				
			||||||
 | 
						h := NewHasher(t, int64(len(content)))
 | 
				
			||||||
 | 
						_, _ = h.Write(content)
 | 
				
			||||||
 | 
						return h.Sum()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Hasher is a struct that will generate a SHA1
 | 
				
			||||||
 | 
					type Hasher struct {
 | 
				
			||||||
 | 
						hash.Hash
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewHasher takes an object type and size and creates a hasher to generate a SHA
 | 
				
			||||||
 | 
					func NewHasher(t ObjectType, size int64) Hasher {
 | 
				
			||||||
 | 
						h := Hasher{sha1.New()}
 | 
				
			||||||
 | 
						_, _ = h.Write(t.Bytes())
 | 
				
			||||||
 | 
						_, _ = h.Write([]byte(" "))
 | 
				
			||||||
 | 
						_, _ = h.Write([]byte(strconv.FormatInt(size, 10)))
 | 
				
			||||||
 | 
						_, _ = h.Write([]byte{0})
 | 
				
			||||||
 | 
						return h
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sum generates a SHA1 for the provided hash
 | 
				
			||||||
 | 
					func (h Hasher) Sum() (sha1 SHA1) {
 | 
				
			||||||
 | 
						copy(sha1[:], h.Hash.Sum(nil))
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,53 +5,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Signature represents the Author or Committer information.
 | 
					 | 
				
			||||||
type Signature = object.Signature
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// GitTimeLayout is the (default) time layout used by git.
 | 
						// GitTimeLayout is the (default) time layout used by git.
 | 
				
			||||||
	GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700"
 | 
						GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Helper to get a signature from the commit line, which looks like these:
 | 
					 | 
				
			||||||
//     author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
 | 
					 | 
				
			||||||
//     author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
 | 
					 | 
				
			||||||
// but without the "author " at the beginning (this method should)
 | 
					 | 
				
			||||||
// be used for author and committer.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// FIXME: include timezone for timestamp!
 | 
					 | 
				
			||||||
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
 | 
					 | 
				
			||||||
	sig := new(Signature)
 | 
					 | 
				
			||||||
	emailStart := bytes.IndexByte(line, '<')
 | 
					 | 
				
			||||||
	sig.Name = string(line[:emailStart-1])
 | 
					 | 
				
			||||||
	emailEnd := bytes.IndexByte(line, '>')
 | 
					 | 
				
			||||||
	sig.Email = string(line[emailStart+1 : emailEnd])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Check date format.
 | 
					 | 
				
			||||||
	if len(line) > emailEnd+2 {
 | 
					 | 
				
			||||||
		firstChar := line[emailEnd+2]
 | 
					 | 
				
			||||||
		if firstChar >= 48 && firstChar <= 57 {
 | 
					 | 
				
			||||||
			timestop := bytes.IndexByte(line[emailEnd+2:], ' ')
 | 
					 | 
				
			||||||
			timestring := string(line[emailEnd+2 : emailEnd+2+timestop])
 | 
					 | 
				
			||||||
			seconds, _ := strconv.ParseInt(timestring, 10, 64)
 | 
					 | 
				
			||||||
			sig.When = time.Unix(seconds, 0)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		// Fall back to unix 0 time
 | 
					 | 
				
			||||||
		sig.When = time.Unix(0, 0)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return sig, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										54
									
								
								modules/git/signature_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								modules/git/signature_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Signature represents the Author or Committer information.
 | 
				
			||||||
 | 
					type Signature = object.Signature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper to get a signature from the commit line, which looks like these:
 | 
				
			||||||
 | 
					//     author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
 | 
				
			||||||
 | 
					//     author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
 | 
				
			||||||
 | 
					// but without the "author " at the beginning (this method should)
 | 
				
			||||||
 | 
					// be used for author and committer.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// FIXME: include timezone for timestamp!
 | 
				
			||||||
 | 
					func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
 | 
				
			||||||
 | 
						sig := new(Signature)
 | 
				
			||||||
 | 
						emailStart := bytes.IndexByte(line, '<')
 | 
				
			||||||
 | 
						sig.Name = string(line[:emailStart-1])
 | 
				
			||||||
 | 
						emailEnd := bytes.IndexByte(line, '>')
 | 
				
			||||||
 | 
						sig.Email = string(line[emailStart+1 : emailEnd])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check date format.
 | 
				
			||||||
 | 
						if len(line) > emailEnd+2 {
 | 
				
			||||||
 | 
							firstChar := line[emailEnd+2]
 | 
				
			||||||
 | 
							if firstChar >= 48 && firstChar <= 57 {
 | 
				
			||||||
 | 
								timestop := bytes.IndexByte(line[emailEnd+2:], ' ')
 | 
				
			||||||
 | 
								timestring := string(line[emailEnd+2 : emailEnd+2+timestop])
 | 
				
			||||||
 | 
								seconds, _ := strconv.ParseInt(timestring, 10, 64)
 | 
				
			||||||
 | 
								sig.When = time.Unix(seconds, 0)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Fall back to unix 0 time
 | 
				
			||||||
 | 
							sig.When = time.Unix(0, 0)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return sig, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										95
									
								
								modules/git/signature_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								modules/git/signature_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Signature represents the Author or Committer information.
 | 
				
			||||||
 | 
					type Signature struct {
 | 
				
			||||||
 | 
						// Name represents a person name. It is an arbitrary string.
 | 
				
			||||||
 | 
						Name string
 | 
				
			||||||
 | 
						// Email is an email, but it cannot be assumed to be well-formed.
 | 
				
			||||||
 | 
						Email string
 | 
				
			||||||
 | 
						// When is the timestamp of the signature.
 | 
				
			||||||
 | 
						When time.Time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Signature) String() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s <%s>", s.Name, s.Email)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Decode decodes a byte array representing a signature to signature
 | 
				
			||||||
 | 
					func (s *Signature) Decode(b []byte) {
 | 
				
			||||||
 | 
						sig, _ := newSignatureFromCommitline(b)
 | 
				
			||||||
 | 
						s.Email = sig.Email
 | 
				
			||||||
 | 
						s.Name = sig.Name
 | 
				
			||||||
 | 
						s.When = sig.When
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper to get a signature from the commit line, which looks like these:
 | 
				
			||||||
 | 
					//     author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
 | 
				
			||||||
 | 
					//     author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
 | 
				
			||||||
 | 
					// but without the "author " at the beginning (this method should)
 | 
				
			||||||
 | 
					// be used for author and committer.
 | 
				
			||||||
 | 
					func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
 | 
				
			||||||
 | 
						sig = new(Signature)
 | 
				
			||||||
 | 
						emailStart := bytes.LastIndexByte(line, '<')
 | 
				
			||||||
 | 
						emailEnd := bytes.LastIndexByte(line, '>')
 | 
				
			||||||
 | 
						if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sig.Name = string(line[:emailStart-1])
 | 
				
			||||||
 | 
						sig.Email = string(line[emailStart+1 : emailEnd])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hasTime := emailEnd+2 < len(line)
 | 
				
			||||||
 | 
						if !hasTime {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check date format.
 | 
				
			||||||
 | 
						firstChar := line[emailEnd+2]
 | 
				
			||||||
 | 
						if firstChar >= 48 && firstChar <= 57 {
 | 
				
			||||||
 | 
							idx := bytes.IndexByte(line[emailEnd+2:], ' ')
 | 
				
			||||||
 | 
							if idx < 0 {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							timestring := string(line[emailEnd+2 : emailEnd+2+idx])
 | 
				
			||||||
 | 
							seconds, _ := strconv.ParseInt(timestring, 10, 64)
 | 
				
			||||||
 | 
							sig.When = time.Unix(seconds, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							idx += emailEnd + 3
 | 
				
			||||||
 | 
							if idx >= len(line) || idx+5 > len(line) {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							timezone := string(line[idx : idx+5])
 | 
				
			||||||
 | 
							tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64)
 | 
				
			||||||
 | 
							tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64)
 | 
				
			||||||
 | 
							if err1 != nil || err2 != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if tzhours < 0 {
 | 
				
			||||||
 | 
								tzmins *= -1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							tz := time.FixedZone("", int(tzhours*60*60+tzmins*60))
 | 
				
			||||||
 | 
							sig.When = sig.When.In(tz)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:]))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,15 +10,19 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const beginpgp = "\n-----BEGIN PGP SIGNATURE-----\n"
 | 
				
			||||||
 | 
					const endpgp = "\n-----END PGP SIGNATURE-----"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tag represents a Git tag.
 | 
					// Tag represents a Git tag.
 | 
				
			||||||
type Tag struct {
 | 
					type Tag struct {
 | 
				
			||||||
	Name    string
 | 
						Name      string
 | 
				
			||||||
	ID      SHA1
 | 
						ID        SHA1
 | 
				
			||||||
	repo    *Repository
 | 
						repo      *Repository
 | 
				
			||||||
	Object  SHA1 // The id of this commit object
 | 
						Object    SHA1 // The id of this commit object
 | 
				
			||||||
	Type    string
 | 
						Type      string
 | 
				
			||||||
	Tagger  *Signature
 | 
						Tagger    *Signature
 | 
				
			||||||
	Message string
 | 
						Message   string
 | 
				
			||||||
 | 
						Signature *CommitGPGSignature
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Commit return the commit of the tag reference
 | 
					// Commit return the commit of the tag reference
 | 
				
			||||||
@@ -60,12 +64,23 @@ l:
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			nextline += eol + 1
 | 
								nextline += eol + 1
 | 
				
			||||||
		case eol == 0:
 | 
							case eol == 0:
 | 
				
			||||||
			tag.Message = strings.TrimRight(string(data[nextline+1:]), "\n")
 | 
								tag.Message = string(data[nextline+1 : len(data)-1])
 | 
				
			||||||
			break l
 | 
								break l
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			break l
 | 
								break l
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						idx := strings.LastIndex(tag.Message, beginpgp)
 | 
				
			||||||
 | 
						if idx > 0 {
 | 
				
			||||||
 | 
							endSigIdx := strings.Index(tag.Message[idx:], endpgp)
 | 
				
			||||||
 | 
							if endSigIdx > 0 {
 | 
				
			||||||
 | 
								tag.Signature = &CommitGPGSignature{
 | 
				
			||||||
 | 
									Signature: tag.Message[idx+1 : idx+endSigIdx+len(endpgp)],
 | 
				
			||||||
 | 
									Payload:   string(data[:bytes.LastIndex(data, []byte(beginpgp))+1]),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								tag.Message = tag.Message[:idx+1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return tag, nil
 | 
						return tag, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,25 +6,9 @@
 | 
				
			|||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Tree represents a flat directory listing.
 | 
					 | 
				
			||||||
type Tree struct {
 | 
					 | 
				
			||||||
	ID         SHA1
 | 
					 | 
				
			||||||
	ResolvedID SHA1
 | 
					 | 
				
			||||||
	repo       *Repository
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gogitTree *object.Tree
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// parent tree
 | 
					 | 
				
			||||||
	ptree *Tree
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewTree create a new tree according the repository and tree id
 | 
					// NewTree create a new tree according the repository and tree id
 | 
				
			||||||
func NewTree(repo *Repository, id SHA1) *Tree {
 | 
					func NewTree(repo *Repository, id SHA1) *Tree {
 | 
				
			||||||
	return &Tree{
 | 
						return &Tree{
 | 
				
			||||||
@@ -61,70 +45,3 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return g, nil
 | 
						return g, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (t *Tree) loadTreeObject() error {
 | 
					 | 
				
			||||||
	gogitTree, err := t.repo.gogitRepo.TreeObject(t.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.gogitTree = gogitTree
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ListEntries returns all entries of current tree.
 | 
					 | 
				
			||||||
func (t *Tree) ListEntries() (Entries, error) {
 | 
					 | 
				
			||||||
	if t.gogitTree == nil {
 | 
					 | 
				
			||||||
		err := t.loadTreeObject()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	entries := make([]*TreeEntry, len(t.gogitTree.Entries))
 | 
					 | 
				
			||||||
	for i, entry := range t.gogitTree.Entries {
 | 
					 | 
				
			||||||
		entries[i] = &TreeEntry{
 | 
					 | 
				
			||||||
			ID:             entry.Hash,
 | 
					 | 
				
			||||||
			gogitTreeEntry: &t.gogitTree.Entries[i],
 | 
					 | 
				
			||||||
			ptree:          t,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return entries, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
 | 
					 | 
				
			||||||
func (t *Tree) ListEntriesRecursive() (Entries, error) {
 | 
					 | 
				
			||||||
	if t.gogitTree == nil {
 | 
					 | 
				
			||||||
		err := t.loadTreeObject()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var entries []*TreeEntry
 | 
					 | 
				
			||||||
	seen := map[plumbing.Hash]bool{}
 | 
					 | 
				
			||||||
	walker := object.NewTreeWalker(t.gogitTree, true, seen)
 | 
					 | 
				
			||||||
	for {
 | 
					 | 
				
			||||||
		fullName, entry, err := walker.Next()
 | 
					 | 
				
			||||||
		if err == io.EOF {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if seen[entry.Hash] {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		convertedEntry := &TreeEntry{
 | 
					 | 
				
			||||||
			ID:             entry.Hash,
 | 
					 | 
				
			||||||
			gogitTreeEntry: &entry,
 | 
					 | 
				
			||||||
			ptree:          t,
 | 
					 | 
				
			||||||
			fullName:       fullName,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		entries = append(entries, convertedEntry)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return entries, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,64 +5,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/filemode"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetTreeEntryByPath get the tree entries according the sub dir
 | 
					 | 
				
			||||||
func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 | 
					 | 
				
			||||||
	if len(relpath) == 0 {
 | 
					 | 
				
			||||||
		return &TreeEntry{
 | 
					 | 
				
			||||||
			ID: t.ID,
 | 
					 | 
				
			||||||
			//Type: ObjectTree,
 | 
					 | 
				
			||||||
			gogitTreeEntry: &object.TreeEntry{
 | 
					 | 
				
			||||||
				Name: "",
 | 
					 | 
				
			||||||
				Mode: filemode.Dir,
 | 
					 | 
				
			||||||
				Hash: t.ID,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	relpath = path.Clean(relpath)
 | 
					 | 
				
			||||||
	parts := strings.Split(relpath, "/")
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	tree := t
 | 
					 | 
				
			||||||
	for i, name := range parts {
 | 
					 | 
				
			||||||
		if i == len(parts)-1 {
 | 
					 | 
				
			||||||
			entries, err := tree.ListEntries()
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				if err == plumbing.ErrObjectNotFound {
 | 
					 | 
				
			||||||
					return nil, ErrNotExist{
 | 
					 | 
				
			||||||
						RelPath: relpath,
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			for _, v := range entries {
 | 
					 | 
				
			||||||
				if v.Name() == name {
 | 
					 | 
				
			||||||
					return v, nil
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			tree, err = tree.SubTree(name)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				if err == plumbing.ErrObjectNotFound {
 | 
					 | 
				
			||||||
					return nil, ErrNotExist{
 | 
					 | 
				
			||||||
						RelPath: relpath,
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil, ErrNotExist{"", relpath}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetBlobByPath get the blob object according the path
 | 
					// GetBlobByPath get the blob object according the path
 | 
				
			||||||
func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
 | 
					func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
 | 
				
			||||||
	entry, err := t.GetTreeEntryByPath(relpath)
 | 
						entry, err := t.GetTreeEntryByPath(relpath)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										66
									
								
								modules/git/tree_blob_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								modules/git/tree_blob_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/filemode"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTreeEntryByPath get the tree entries according the sub dir
 | 
				
			||||||
 | 
					func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 | 
				
			||||||
 | 
						if len(relpath) == 0 {
 | 
				
			||||||
 | 
							return &TreeEntry{
 | 
				
			||||||
 | 
								ID: t.ID,
 | 
				
			||||||
 | 
								//Type: ObjectTree,
 | 
				
			||||||
 | 
								gogitTreeEntry: &object.TreeEntry{
 | 
				
			||||||
 | 
									Name: "",
 | 
				
			||||||
 | 
									Mode: filemode.Dir,
 | 
				
			||||||
 | 
									Hash: t.ID,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						relpath = path.Clean(relpath)
 | 
				
			||||||
 | 
						parts := strings.Split(relpath, "/")
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						tree := t
 | 
				
			||||||
 | 
						for i, name := range parts {
 | 
				
			||||||
 | 
							if i == len(parts)-1 {
 | 
				
			||||||
 | 
								entries, err := tree.ListEntries()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if err == plumbing.ErrObjectNotFound {
 | 
				
			||||||
 | 
										return nil, ErrNotExist{
 | 
				
			||||||
 | 
											RelPath: relpath,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for _, v := range entries {
 | 
				
			||||||
 | 
									if v.Name() == name {
 | 
				
			||||||
 | 
										return v, nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								tree, err = tree.SubTree(name)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if err == plumbing.ErrObjectNotFound {
 | 
				
			||||||
 | 
										return nil, ErrNotExist{
 | 
				
			||||||
 | 
											RelPath: relpath,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, ErrNotExist{"", relpath}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								modules/git/tree_blob_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								modules/git/tree_blob_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTreeEntryByPath get the tree entries according the sub dir
 | 
				
			||||||
 | 
					func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 | 
				
			||||||
 | 
						if len(relpath) == 0 {
 | 
				
			||||||
 | 
							return &TreeEntry{
 | 
				
			||||||
 | 
								ID:        t.ID,
 | 
				
			||||||
 | 
								name:      "",
 | 
				
			||||||
 | 
								fullName:  "",
 | 
				
			||||||
 | 
								entryMode: EntryModeTree,
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: This should probably use git cat-file --batch to be a bit more efficient
 | 
				
			||||||
 | 
						relpath = path.Clean(relpath)
 | 
				
			||||||
 | 
						parts := strings.Split(relpath, "/")
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						tree := t
 | 
				
			||||||
 | 
						for i, name := range parts {
 | 
				
			||||||
 | 
							if i == len(parts)-1 {
 | 
				
			||||||
 | 
								entries, err := tree.ListEntries()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for _, v := range entries {
 | 
				
			||||||
 | 
									if v.Name() == name {
 | 
				
			||||||
 | 
										return v, nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								tree, err = tree.SubTree(name)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, ErrNotExist{"", relpath}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -9,55 +9,8 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/filemode"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EntryMode the type of the object in the git tree
 | 
					 | 
				
			||||||
type EntryMode int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// There are only a few file modes in Git. They look like unix file modes, but they can only be
 | 
					 | 
				
			||||||
// one of these.
 | 
					 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	// EntryModeBlob
 | 
					 | 
				
			||||||
	EntryModeBlob EntryMode = 0100644
 | 
					 | 
				
			||||||
	// EntryModeExec
 | 
					 | 
				
			||||||
	EntryModeExec EntryMode = 0100755
 | 
					 | 
				
			||||||
	// EntryModeSymlink
 | 
					 | 
				
			||||||
	EntryModeSymlink EntryMode = 0120000
 | 
					 | 
				
			||||||
	// EntryModeCommit
 | 
					 | 
				
			||||||
	EntryModeCommit EntryMode = 0160000
 | 
					 | 
				
			||||||
	// EntryModeTree
 | 
					 | 
				
			||||||
	EntryModeTree EntryMode = 0040000
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TreeEntry the leaf in the git tree
 | 
					 | 
				
			||||||
type TreeEntry struct {
 | 
					 | 
				
			||||||
	ID SHA1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gogitTreeEntry *object.TreeEntry
 | 
					 | 
				
			||||||
	ptree          *Tree
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	size     int64
 | 
					 | 
				
			||||||
	sized    bool
 | 
					 | 
				
			||||||
	fullName string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Name returns the name of the entry
 | 
					 | 
				
			||||||
func (te *TreeEntry) Name() string {
 | 
					 | 
				
			||||||
	if te.fullName != "" {
 | 
					 | 
				
			||||||
		return te.fullName
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return te.gogitTreeEntry.Name
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Mode returns the mode of the entry
 | 
					 | 
				
			||||||
func (te *TreeEntry) Mode() EntryMode {
 | 
					 | 
				
			||||||
	return EntryMode(te.gogitTreeEntry.Mode)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Type returns the type of the entry (commit, tree, blob)
 | 
					// Type returns the type of the entry (commit, tree, blob)
 | 
				
			||||||
func (te *TreeEntry) Type() string {
 | 
					func (te *TreeEntry) Type() string {
 | 
				
			||||||
	switch te.Mode() {
 | 
						switch te.Mode() {
 | 
				
			||||||
@@ -70,63 +23,6 @@ func (te *TreeEntry) Type() string {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Size returns the size of the entry
 | 
					 | 
				
			||||||
func (te *TreeEntry) Size() int64 {
 | 
					 | 
				
			||||||
	if te.IsDir() {
 | 
					 | 
				
			||||||
		return 0
 | 
					 | 
				
			||||||
	} else if te.sized {
 | 
					 | 
				
			||||||
		return te.size
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	file, err := te.ptree.gogitTree.TreeEntryFile(te.gogitTreeEntry)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return 0
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	te.sized = true
 | 
					 | 
				
			||||||
	te.size = file.Size
 | 
					 | 
				
			||||||
	return te.size
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsSubModule if the entry is a sub module
 | 
					 | 
				
			||||||
func (te *TreeEntry) IsSubModule() bool {
 | 
					 | 
				
			||||||
	return te.gogitTreeEntry.Mode == filemode.Submodule
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsDir if the entry is a sub dir
 | 
					 | 
				
			||||||
func (te *TreeEntry) IsDir() bool {
 | 
					 | 
				
			||||||
	return te.gogitTreeEntry.Mode == filemode.Dir
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsLink if the entry is a symlink
 | 
					 | 
				
			||||||
func (te *TreeEntry) IsLink() bool {
 | 
					 | 
				
			||||||
	return te.gogitTreeEntry.Mode == filemode.Symlink
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsRegular if the entry is a regular file
 | 
					 | 
				
			||||||
func (te *TreeEntry) IsRegular() bool {
 | 
					 | 
				
			||||||
	return te.gogitTreeEntry.Mode == filemode.Regular
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsExecutable if the entry is an executable file (not necessarily binary)
 | 
					 | 
				
			||||||
func (te *TreeEntry) IsExecutable() bool {
 | 
					 | 
				
			||||||
	return te.gogitTreeEntry.Mode == filemode.Executable
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Blob returns the blob object the entry
 | 
					 | 
				
			||||||
func (te *TreeEntry) Blob() *Blob {
 | 
					 | 
				
			||||||
	encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &Blob{
 | 
					 | 
				
			||||||
		ID:              te.gogitTreeEntry.Hash,
 | 
					 | 
				
			||||||
		gogitEncodedObj: encodedObj,
 | 
					 | 
				
			||||||
		name:            te.Name(),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FollowLink returns the entry pointed to by a symlink
 | 
					// FollowLink returns the entry pointed to by a symlink
 | 
				
			||||||
func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
 | 
					func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
 | 
				
			||||||
	if !te.IsLink() {
 | 
						if !te.IsLink() {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										96
									
								
								modules/git/tree_entry_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								modules/git/tree_entry_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/filemode"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TreeEntry the leaf in the git tree
 | 
				
			||||||
 | 
					type TreeEntry struct {
 | 
				
			||||||
 | 
						ID SHA1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gogitTreeEntry *object.TreeEntry
 | 
				
			||||||
 | 
						ptree          *Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size     int64
 | 
				
			||||||
 | 
						sized    bool
 | 
				
			||||||
 | 
						fullName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Name returns the name of the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Name() string {
 | 
				
			||||||
 | 
						if te.fullName != "" {
 | 
				
			||||||
 | 
							return te.fullName
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return te.gogitTreeEntry.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Mode returns the mode of the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Mode() EntryMode {
 | 
				
			||||||
 | 
						return EntryMode(te.gogitTreeEntry.Mode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Size returns the size of the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Size() int64 {
 | 
				
			||||||
 | 
						if te.IsDir() {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						} else if te.sized {
 | 
				
			||||||
 | 
							return te.size
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						file, err := te.ptree.gogitTree.TreeEntryFile(te.gogitTreeEntry)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						te.sized = true
 | 
				
			||||||
 | 
						te.size = file.Size
 | 
				
			||||||
 | 
						return te.size
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsSubModule if the entry is a sub module
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsSubModule() bool {
 | 
				
			||||||
 | 
						return te.gogitTreeEntry.Mode == filemode.Submodule
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsDir if the entry is a sub dir
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsDir() bool {
 | 
				
			||||||
 | 
						return te.gogitTreeEntry.Mode == filemode.Dir
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsLink if the entry is a symlink
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsLink() bool {
 | 
				
			||||||
 | 
						return te.gogitTreeEntry.Mode == filemode.Symlink
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRegular if the entry is a regular file
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsRegular() bool {
 | 
				
			||||||
 | 
						return te.gogitTreeEntry.Mode == filemode.Regular
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsExecutable if the entry is an executable file (not necessarily binary)
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsExecutable() bool {
 | 
				
			||||||
 | 
						return te.gogitTreeEntry.Mode == filemode.Executable
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Blob returns the blob object the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Blob() *Blob {
 | 
				
			||||||
 | 
						encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &Blob{
 | 
				
			||||||
 | 
							ID:              te.gogitTreeEntry.Hash,
 | 
				
			||||||
 | 
							gogitEncodedObj: encodedObj,
 | 
				
			||||||
 | 
							name:            te.Name(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								modules/git/tree_entry_mode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								modules/git/tree_entry_mode.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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 git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EntryMode the type of the object in the git tree
 | 
				
			||||||
 | 
					type EntryMode int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// There are only a few file modes in Git. They look like unix file modes, but they can only be
 | 
				
			||||||
 | 
					// one of these.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// EntryModeBlob
 | 
				
			||||||
 | 
						EntryModeBlob EntryMode = 0100644
 | 
				
			||||||
 | 
						// EntryModeExec
 | 
				
			||||||
 | 
						EntryModeExec EntryMode = 0100755
 | 
				
			||||||
 | 
						// EntryModeSymlink
 | 
				
			||||||
 | 
						EntryModeSymlink EntryMode = 0120000
 | 
				
			||||||
 | 
						// EntryModeCommit
 | 
				
			||||||
 | 
						EntryModeCommit EntryMode = 0160000
 | 
				
			||||||
 | 
						// EntryModeTree
 | 
				
			||||||
 | 
						EntryModeTree EntryMode = 0040000
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String converts an EntryMode to a string
 | 
				
			||||||
 | 
					func (e EntryMode) String() string {
 | 
				
			||||||
 | 
						return strconv.FormatInt(int64(e), 8)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToEntryMode converts a string to an EntryMode
 | 
				
			||||||
 | 
					func ToEntryMode(value string) EntryMode {
 | 
				
			||||||
 | 
						v, _ := strconv.ParseInt(value, 8, 32)
 | 
				
			||||||
 | 
						return EntryMode(v)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										91
									
								
								modules/git/tree_entry_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								modules/git/tree_entry_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TreeEntry the leaf in the git tree
 | 
				
			||||||
 | 
					type TreeEntry struct {
 | 
				
			||||||
 | 
						ID SHA1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ptree *Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entryMode EntryMode
 | 
				
			||||||
 | 
						name      string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size     int64
 | 
				
			||||||
 | 
						sized    bool
 | 
				
			||||||
 | 
						fullName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Name returns the name of the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Name() string {
 | 
				
			||||||
 | 
						if te.fullName != "" {
 | 
				
			||||||
 | 
							return te.fullName
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return te.name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Mode returns the mode of the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Mode() EntryMode {
 | 
				
			||||||
 | 
						return te.entryMode
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Size returns the size of the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Size() int64 {
 | 
				
			||||||
 | 
						if te.IsDir() {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						} else if te.sized {
 | 
				
			||||||
 | 
							return te.size
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdout, err := NewCommand("cat-file", "-s", te.ID.String()).RunInDir(te.ptree.repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						te.sized = true
 | 
				
			||||||
 | 
						te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
 | 
				
			||||||
 | 
						return te.size
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsSubModule if the entry is a sub module
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsSubModule() bool {
 | 
				
			||||||
 | 
						return te.entryMode == EntryModeCommit
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsDir if the entry is a sub dir
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsDir() bool {
 | 
				
			||||||
 | 
						return te.entryMode == EntryModeTree
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsLink if the entry is a symlink
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsLink() bool {
 | 
				
			||||||
 | 
						return te.entryMode == EntryModeSymlink
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsRegular if the entry is a regular file
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsRegular() bool {
 | 
				
			||||||
 | 
						return te.entryMode == EntryModeBlob
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsExecutable if the entry is an executable file (not necessarily binary)
 | 
				
			||||||
 | 
					func (te *TreeEntry) IsExecutable() bool {
 | 
				
			||||||
 | 
						return te.entryMode == EntryModeExec
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Blob returns the blob object the entry
 | 
				
			||||||
 | 
					func (te *TreeEntry) Blob() *Blob {
 | 
				
			||||||
 | 
						return &Blob{
 | 
				
			||||||
 | 
							ID:       te.ID,
 | 
				
			||||||
 | 
							repoPath: te.ptree.repo.Path,
 | 
				
			||||||
 | 
							name:     te.Name(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,6 +2,8 @@
 | 
				
			|||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										94
									
								
								modules/git/tree_gogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								modules/git/tree_gogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/object"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Tree represents a flat directory listing.
 | 
				
			||||||
 | 
					type Tree struct {
 | 
				
			||||||
 | 
						ID         SHA1
 | 
				
			||||||
 | 
						ResolvedID SHA1
 | 
				
			||||||
 | 
						repo       *Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gogitTree *object.Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// parent tree
 | 
				
			||||||
 | 
						ptree *Tree
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Tree) loadTreeObject() error {
 | 
				
			||||||
 | 
						gogitTree, err := t.repo.gogitRepo.TreeObject(t.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.gogitTree = gogitTree
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListEntries returns all entries of current tree.
 | 
				
			||||||
 | 
					func (t *Tree) ListEntries() (Entries, error) {
 | 
				
			||||||
 | 
						if t.gogitTree == nil {
 | 
				
			||||||
 | 
							err := t.loadTreeObject()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entries := make([]*TreeEntry, len(t.gogitTree.Entries))
 | 
				
			||||||
 | 
						for i, entry := range t.gogitTree.Entries {
 | 
				
			||||||
 | 
							entries[i] = &TreeEntry{
 | 
				
			||||||
 | 
								ID:             entry.Hash,
 | 
				
			||||||
 | 
								gogitTreeEntry: &t.gogitTree.Entries[i],
 | 
				
			||||||
 | 
								ptree:          t,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return entries, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
 | 
				
			||||||
 | 
					func (t *Tree) ListEntriesRecursive() (Entries, error) {
 | 
				
			||||||
 | 
						if t.gogitTree == nil {
 | 
				
			||||||
 | 
							err := t.loadTreeObject()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var entries []*TreeEntry
 | 
				
			||||||
 | 
						seen := map[plumbing.Hash]bool{}
 | 
				
			||||||
 | 
						walker := object.NewTreeWalker(t.gogitTree, true, seen)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							fullName, entry, err := walker.Next()
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if seen[entry.Hash] {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							convertedEntry := &TreeEntry{
 | 
				
			||||||
 | 
								ID:             entry.Hash,
 | 
				
			||||||
 | 
								gogitTreeEntry: &entry,
 | 
				
			||||||
 | 
								ptree:          t,
 | 
				
			||||||
 | 
								fullName:       fullName,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							entries = append(entries, convertedEntry)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return entries, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										69
									
								
								modules/git/tree_nogogit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								modules/git/tree_nogogit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build !gogit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Tree represents a flat directory listing.
 | 
				
			||||||
 | 
					type Tree struct {
 | 
				
			||||||
 | 
						ID         SHA1
 | 
				
			||||||
 | 
						ResolvedID SHA1
 | 
				
			||||||
 | 
						repo       *Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// parent tree
 | 
				
			||||||
 | 
						ptree *Tree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entries       Entries
 | 
				
			||||||
 | 
						entriesParsed bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entriesRecursive       Entries
 | 
				
			||||||
 | 
						entriesRecursiveParsed bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListEntries returns all entries of current tree.
 | 
				
			||||||
 | 
					func (t *Tree) ListEntries() (Entries, error) {
 | 
				
			||||||
 | 
						if t.entriesParsed {
 | 
				
			||||||
 | 
							return t.entries, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "fatal: not a tree object") {
 | 
				
			||||||
 | 
								return nil, ErrNotExist{
 | 
				
			||||||
 | 
									ID: t.ID.String(),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.entries, err = parseTreeEntries(stdout, t)
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							t.entriesParsed = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return t.entries, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
 | 
				
			||||||
 | 
					func (t *Tree) ListEntriesRecursive() (Entries, error) {
 | 
				
			||||||
 | 
						if t.entriesRecursiveParsed {
 | 
				
			||||||
 | 
							return t.entriesRecursive, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						stdout, err := NewCommand("ls-tree", "-t", "-r", t.ID.String()).RunInDirBytes(t.repo.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.entriesRecursive, err = parseTreeEntries(stdout, t)
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							t.entriesRecursiveParsed = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return t.entriesRecursive, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,6 +6,7 @@ package git
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@@ -68,11 +69,12 @@ func isExist(path string) bool {
 | 
				
			|||||||
	return err == nil || os.IsExist(err)
 | 
						return err == nil || os.IsExist(err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func concatenateError(err error, stderr string) error {
 | 
					// ConcatenateError concatenats an error with stderr string
 | 
				
			||||||
 | 
					func ConcatenateError(err error, stderr string) error {
 | 
				
			||||||
	if len(stderr) == 0 {
 | 
						if len(stderr) == 0 {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return fmt.Errorf("%v - %s", err, stderr)
 | 
						return fmt.Errorf("%w - %s", err, stderr)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RefEndName return the end name of a ref name
 | 
					// RefEndName return the end name of a ref name
 | 
				
			||||||
@@ -140,3 +142,29 @@ func ParseBool(value string) (result bool, valid bool) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return intValue != 0, true
 | 
						return intValue != 0, true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LimitedReaderCloser is a limited reader closer
 | 
				
			||||||
 | 
					type LimitedReaderCloser struct {
 | 
				
			||||||
 | 
						R io.Reader
 | 
				
			||||||
 | 
						C io.Closer
 | 
				
			||||||
 | 
						N int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read implements io.Reader
 | 
				
			||||||
 | 
					func (l *LimitedReaderCloser) Read(p []byte) (n int, err error) {
 | 
				
			||||||
 | 
						if l.N <= 0 {
 | 
				
			||||||
 | 
							_ = l.C.Close()
 | 
				
			||||||
 | 
							return 0, io.EOF
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if int64(len(p)) > l.N {
 | 
				
			||||||
 | 
							p = p[0:l.N]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						n, err = l.R.Read(p)
 | 
				
			||||||
 | 
						l.N -= int64(n)
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Close implements io.Closer
 | 
				
			||||||
 | 
					func (l *LimitedReaderCloser) Close() error {
 | 
				
			||||||
 | 
						return l.C.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package stats
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DBIndexer implements Indexer interface to use database's like search
 | 
					// DBIndexer implements Indexer interface to use database's like search
 | 
				
			||||||
@@ -37,6 +38,7 @@ func (db *DBIndexer) Index(id int64) error {
 | 
				
			|||||||
	// Get latest commit for default branch
 | 
						// Get latest commit for default branch
 | 
				
			||||||
	commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
 | 
						commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("Unable to get commit ID for defaultbranch %s in %s", repo.DefaultBranch, repo.RepoPath())
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,6 +50,7 @@ func (db *DBIndexer) Index(id int64) error {
 | 
				
			|||||||
	// Calculate and save language statistics to database
 | 
						// Calculate and save language statistics to database
 | 
				
			||||||
	stats, err := gitRepo.GetLanguageStats(commitID)
 | 
						stats, err := gitRepo.GetLanguageStats(commitID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("Unable to get language stats for ID %s for defaultbranch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return repo.UpdateLanguageStats(commitID, stats)
 | 
						return repo.UpdateLanguageStats(commitID, stats)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,57 +5,14 @@
 | 
				
			|||||||
package repository
 | 
					package repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/cache"
 | 
						"code.gitea.io/gitea/modules/cache"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func recusiveCache(gitRepo *git.Repository, c cgobject.CommitNode, tree *git.Tree, treePath string, ca *cache.LastCommitCache, level int) error {
 | 
					 | 
				
			||||||
	if level == 0 {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	entries, err := tree.ListEntries()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	entryPaths := make([]string, len(entries))
 | 
					 | 
				
			||||||
	entryMap := make(map[string]*git.TreeEntry)
 | 
					 | 
				
			||||||
	for i, entry := range entries {
 | 
					 | 
				
			||||||
		entryPaths[i] = entry.Name()
 | 
					 | 
				
			||||||
		entryMap[entry.Name()] = entry
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commits, err := git.GetLastCommitForPaths(c, treePath, entryPaths)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for entry, cm := range commits {
 | 
					 | 
				
			||||||
		if err := ca.Put(c.ID().String(), path.Join(treePath, entry), cm.ID().String()); err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if entryMap[entry].IsDir() {
 | 
					 | 
				
			||||||
			subTree, err := tree.SubTree(entry)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if err := recusiveCache(gitRepo, c, subTree, entry, ca, level-1); err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getRefName(fullRefName string) string {
 | 
					func getRefName(fullRefName string) string {
 | 
				
			||||||
	if strings.HasPrefix(fullRefName, git.TagPrefix) {
 | 
						if strings.HasPrefix(fullRefName, git.TagPrefix) {
 | 
				
			||||||
		return fullRefName[len(git.TagPrefix):]
 | 
							return fullRefName[len(git.TagPrefix):]
 | 
				
			||||||
@@ -84,14 +41,7 @@ func CacheRef(repo *models.Repository, gitRepo *git.Repository, fullRefName stri
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	commitNodeIndex, _ := gitRepo.CommitNodeIndex()
 | 
						commitCache := git.NewLastCommitCache(repo.FullName(), gitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds()), cache.GetCache())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c, err := commitNodeIndex.Get(commit.ID)
 | 
						return commitCache.CacheCommit(commit)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ca := cache.NewLastCommitCache(repo.FullName(), gitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds()))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return recusiveCache(gitRepo, c, &commit.Tree, "", ca, 1)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,6 @@ import (
 | 
				
			|||||||
	repo_service "code.gitea.io/gitea/services/repository"
 | 
						repo_service "code.gitea.io/gitea/services/repository"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gitea.com/macaron/macaron"
 | 
						"gitea.com/macaron/macaron"
 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error {
 | 
					func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error {
 | 
				
			||||||
@@ -82,7 +81,7 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
 | 
				
			|||||||
		_ = stdoutReader.Close()
 | 
							_ = stdoutReader.Close()
 | 
				
			||||||
		_ = stdoutWriter.Close()
 | 
							_ = stdoutWriter.Close()
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
	hash := plumbing.NewHash(sha)
 | 
						hash := git.MustIDFromString(sha)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return git.NewCommand("cat-file", "commit", sha).
 | 
						return git.NewCommand("cat-file", "commit", sha).
 | 
				
			||||||
		RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path,
 | 
							RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,9 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"sort"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
@@ -29,9 +27,6 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/storage"
 | 
						"code.gitea.io/gitea/modules/storage"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gogit "github.com/go-git/go-git/v5"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
					 | 
				
			||||||
	"github.com/unknwon/com"
 | 
						"github.com/unknwon/com"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -363,22 +358,6 @@ func LFSDelete(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs")
 | 
						ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type lfsResult struct {
 | 
					 | 
				
			||||||
	Name           string
 | 
					 | 
				
			||||||
	SHA            string
 | 
					 | 
				
			||||||
	Summary        string
 | 
					 | 
				
			||||||
	When           time.Time
 | 
					 | 
				
			||||||
	ParentHashes   []plumbing.Hash
 | 
					 | 
				
			||||||
	BranchName     string
 | 
					 | 
				
			||||||
	FullCommitName string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type lfsResultSlice []*lfsResult
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (a lfsResultSlice) Len() int           { return len(a) }
 | 
					 | 
				
			||||||
func (a lfsResultSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
					 | 
				
			||||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// LFSFileFind guesses a sha for the provided oid (or uses the provided sha) and then finds the commits that contain this sha
 | 
					// LFSFileFind guesses a sha for the provided oid (or uses the provided sha) and then finds the commits that contain this sha
 | 
				
			||||||
func LFSFileFind(ctx *context.Context) {
 | 
					func LFSFileFind(ctx *context.Context) {
 | 
				
			||||||
	if !setting.LFS.StartServer {
 | 
						if !setting.LFS.StartServer {
 | 
				
			||||||
@@ -394,140 +373,27 @@ func LFSFileFind(ctx *context.Context) {
 | 
				
			|||||||
	sha := ctx.Query("sha")
 | 
						sha := ctx.Query("sha")
 | 
				
			||||||
	ctx.Data["Title"] = oid
 | 
						ctx.Data["Title"] = oid
 | 
				
			||||||
	ctx.Data["PageIsSettingsLFS"] = true
 | 
						ctx.Data["PageIsSettingsLFS"] = true
 | 
				
			||||||
	var hash plumbing.Hash
 | 
						var hash git.SHA1
 | 
				
			||||||
	if len(sha) == 0 {
 | 
						if len(sha) == 0 {
 | 
				
			||||||
		meta := models.LFSMetaObject{Oid: oid, Size: size}
 | 
							meta := models.LFSMetaObject{Oid: oid, Size: size}
 | 
				
			||||||
		pointer := meta.Pointer()
 | 
							pointer := meta.Pointer()
 | 
				
			||||||
		hash = plumbing.ComputeHash(plumbing.BlobObject, []byte(pointer))
 | 
							hash = git.ComputeBlobHash([]byte(pointer))
 | 
				
			||||||
		sha = hash.String()
 | 
							sha = hash.String()
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		hash = plumbing.NewHash(sha)
 | 
							hash = git.MustIDFromString(sha)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
 | 
						ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
 | 
				
			||||||
	ctx.Data["Oid"] = oid
 | 
						ctx.Data["Oid"] = oid
 | 
				
			||||||
	ctx.Data["Size"] = size
 | 
						ctx.Data["Size"] = size
 | 
				
			||||||
	ctx.Data["SHA"] = sha
 | 
						ctx.Data["SHA"] = sha
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resultsMap := map[string]*lfsResult{}
 | 
						results, err := pipeline.FindLFSFile(ctx.Repo.GitRepo, hash)
 | 
				
			||||||
	results := make([]*lfsResult, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	basePath := ctx.Repo.Repository.RepoPath()
 | 
					 | 
				
			||||||
	gogitRepo := ctx.Repo.GitRepo.GoGitRepo()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commitsIter, err := gogitRepo.Log(&gogit.LogOptions{
 | 
					 | 
				
			||||||
		Order: gogit.LogOrderCommitterTime,
 | 
					 | 
				
			||||||
		All:   true,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error("Failed to get GoGit CommitsIter: %v", err)
 | 
					 | 
				
			||||||
		ctx.ServerError("LFSFind: Iterate Commits", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
 | 
					 | 
				
			||||||
		tree, err := gitCommit.Tree()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		treeWalker := object.NewTreeWalker(tree, true, nil)
 | 
					 | 
				
			||||||
		defer treeWalker.Close()
 | 
					 | 
				
			||||||
		for {
 | 
					 | 
				
			||||||
			name, entry, err := treeWalker.Next()
 | 
					 | 
				
			||||||
			if err == io.EOF {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if entry.Hash == hash {
 | 
					 | 
				
			||||||
				result := lfsResult{
 | 
					 | 
				
			||||||
					Name:         name,
 | 
					 | 
				
			||||||
					SHA:          gitCommit.Hash.String(),
 | 
					 | 
				
			||||||
					Summary:      strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0],
 | 
					 | 
				
			||||||
					When:         gitCommit.Author.When,
 | 
					 | 
				
			||||||
					ParentHashes: gitCommit.ParentHashes,
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				resultsMap[gitCommit.Hash.String()+":"+name] = &result
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	if err != nil && err != io.EOF {
 | 
						if err != nil && err != io.EOF {
 | 
				
			||||||
		log.Error("Failure in CommitIter.ForEach: %v", err)
 | 
							log.Error("Failure in FindLFSFile: %v", err)
 | 
				
			||||||
		ctx.ServerError("LFSFind: IterateCommits ForEach", err)
 | 
							ctx.ServerError("LFSFind: FindLFSFile.", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, result := range resultsMap {
 | 
					 | 
				
			||||||
		hasParent := false
 | 
					 | 
				
			||||||
		for _, parentHash := range result.ParentHashes {
 | 
					 | 
				
			||||||
			if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent {
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if !hasParent {
 | 
					 | 
				
			||||||
			results = append(results, result)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sort.Sort(lfsResultSlice(results))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
 | 
					 | 
				
			||||||
	shasToNameReader, shasToNameWriter := io.Pipe()
 | 
					 | 
				
			||||||
	nameRevStdinReader, nameRevStdinWriter := io.Pipe()
 | 
					 | 
				
			||||||
	errChan := make(chan error, 1)
 | 
					 | 
				
			||||||
	wg := sync.WaitGroup{}
 | 
					 | 
				
			||||||
	wg.Add(3)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	go func() {
 | 
					 | 
				
			||||||
		defer wg.Done()
 | 
					 | 
				
			||||||
		scanner := bufio.NewScanner(nameRevStdinReader)
 | 
					 | 
				
			||||||
		i := 0
 | 
					 | 
				
			||||||
		for scanner.Scan() {
 | 
					 | 
				
			||||||
			line := scanner.Text()
 | 
					 | 
				
			||||||
			if len(line) == 0 {
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			result := results[i]
 | 
					 | 
				
			||||||
			result.FullCommitName = line
 | 
					 | 
				
			||||||
			result.BranchName = strings.Split(line, "~")[0]
 | 
					 | 
				
			||||||
			i++
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}()
 | 
					 | 
				
			||||||
	go pipeline.NameRevStdin(shasToNameReader, nameRevStdinWriter, &wg, basePath)
 | 
					 | 
				
			||||||
	go func() {
 | 
					 | 
				
			||||||
		defer wg.Done()
 | 
					 | 
				
			||||||
		defer shasToNameWriter.Close()
 | 
					 | 
				
			||||||
		for _, result := range results {
 | 
					 | 
				
			||||||
			i := 0
 | 
					 | 
				
			||||||
			if i < len(result.SHA) {
 | 
					 | 
				
			||||||
				n, err := shasToNameWriter.Write([]byte(result.SHA)[i:])
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					errChan <- err
 | 
					 | 
				
			||||||
					break
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				i += n
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			n := 0
 | 
					 | 
				
			||||||
			for n < 1 {
 | 
					 | 
				
			||||||
				n, err = shasToNameWriter.Write([]byte{'\n'})
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					errChan <- err
 | 
					 | 
				
			||||||
					break
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	wg.Wait()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	select {
 | 
					 | 
				
			||||||
	case err, has := <-errChan:
 | 
					 | 
				
			||||||
		if has {
 | 
					 | 
				
			||||||
			ctx.ServerError("LFSPointerFiles", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ctx.Data["Results"] = results
 | 
						ctx.Data["Results"] = results
 | 
				
			||||||
	ctx.HTML(200, tplSettingsLFSFileFind)
 | 
						ctx.HTML(200, tplSettingsLFSFileFind)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -137,9 +137,9 @@ func renderDirectory(ctx *context.Context, treeLink string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	entries.CustomSort(base.NaturalSortLess)
 | 
						entries.CustomSort(base.NaturalSortLess)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var c git.LastCommitCache
 | 
						var c *git.LastCommitCache
 | 
				
			||||||
	if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
 | 
						if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
 | 
				
			||||||
		c = cache.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds()))
 | 
							c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds()), cache.GetCache())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var latestCommit *git.Commit
 | 
						var latestCommit *git.Commit
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,18 +40,19 @@
 | 
				
			|||||||
			</tr>
 | 
								</tr>
 | 
				
			||||||
		{{end}}
 | 
							{{end}}
 | 
				
			||||||
		{{range $item := .Files}}
 | 
							{{range $item := .Files}}
 | 
				
			||||||
			{{$entry := index $item 0}}
 | 
								{{$entry := $item.Entry}}
 | 
				
			||||||
			{{$commit := index $item 1}}
 | 
								{{$commit := $item.Commit}}
 | 
				
			||||||
 | 
								{{$subModuleFile := $item.SubModuleFile}}
 | 
				
			||||||
			<tr>
 | 
								<tr>
 | 
				
			||||||
				<td class="name four wide">
 | 
									<td class="name four wide">
 | 
				
			||||||
					<span class="truncate">
 | 
										<span class="truncate">
 | 
				
			||||||
						{{if $entry.IsSubModule}}
 | 
											{{if $entry.IsSubModule}}
 | 
				
			||||||
							{{svg "octicon-file-submodule"}}
 | 
												{{svg "octicon-file-submodule"}}
 | 
				
			||||||
							{{$refURL := $commit.RefURL AppUrl $.Repository.FullName $.SSHDomain}}
 | 
												{{$refURL := $subModuleFile.RefURL AppUrl $.Repository.FullName $.SSHDomain}}
 | 
				
			||||||
							{{if $refURL}}
 | 
												{{if $refURL}}
 | 
				
			||||||
								<a href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a href="{{$refURL}}/commit/{{$commit.RefID}}">{{ShortSha $commit.RefID}}</a>
 | 
													<a href="{{$refURL}}">{{$entry.Name}}</a><span class="at">@</span><a href="{{$refURL}}/commit/{{$subModuleFile.RefID}}">{{ShortSha $subModuleFile.RefID}}</a>
 | 
				
			||||||
							{{else}}
 | 
												{{else}}
 | 
				
			||||||
								{{$entry.Name}}<span class="at">@</span>{{ShortSha $commit.RefID}}
 | 
													{{$entry.Name}}<span class="at">@</span>{{ShortSha $subModuleFile.RefID}}
 | 
				
			||||||
							{{end}}
 | 
												{{end}}
 | 
				
			||||||
						{{else}}
 | 
											{{else}}
 | 
				
			||||||
							{{if $entry.IsDir}}
 | 
												{{if $entry.IsDir}}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user