mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	Support getting last commit message using contents-ext API (#34904)
Fix #34870 Fix #34929 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -119,11 +119,14 @@ type ContentsResponse struct {
 | 
				
			|||||||
	Name string `json:"name"`
 | 
						Name string `json:"name"`
 | 
				
			||||||
	Path string `json:"path"`
 | 
						Path string `json:"path"`
 | 
				
			||||||
	SHA  string `json:"sha"`
 | 
						SHA  string `json:"sha"`
 | 
				
			||||||
	LastCommitSHA string `json:"last_commit_sha"`
 | 
					
 | 
				
			||||||
 | 
						LastCommitSHA *string `json:"last_commit_sha,omitempty"`
 | 
				
			||||||
	// swagger:strfmt date-time
 | 
						// swagger:strfmt date-time
 | 
				
			||||||
	LastCommitterDate time.Time `json:"last_committer_date"`
 | 
						LastCommitterDate *time.Time `json:"last_committer_date,omitempty"`
 | 
				
			||||||
	// swagger:strfmt date-time
 | 
						// swagger:strfmt date-time
 | 
				
			||||||
	LastAuthorDate time.Time `json:"last_author_date"`
 | 
						LastAuthorDate    *time.Time `json:"last_author_date,omitempty"`
 | 
				
			||||||
 | 
						LastCommitMessage *string    `json:"last_commit_message,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// `type` will be `file`, `dir`, `symlink`, or `submodule`
 | 
						// `type` will be `file`, `dir`, `symlink`, or `submodule`
 | 
				
			||||||
	Type string `json:"type"`
 | 
						Type string `json:"type"`
 | 
				
			||||||
	Size int64  `json:"size"`
 | 
						Size int64  `json:"size"`
 | 
				
			||||||
@@ -141,8 +144,8 @@ type ContentsResponse struct {
 | 
				
			|||||||
	SubmoduleGitURL *string            `json:"submodule_git_url"`
 | 
						SubmoduleGitURL *string            `json:"submodule_git_url"`
 | 
				
			||||||
	Links           *FileLinksResponse `json:"_links"`
 | 
						Links           *FileLinksResponse `json:"_links"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	LfsOid  *string `json:"lfs_oid"`
 | 
						LfsOid  *string `json:"lfs_oid,omitempty"`
 | 
				
			||||||
	LfsSize *int64  `json:"lfs_size"`
 | 
						LfsSize *int64  `json:"lfs_size,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FileCommitResponse contains information generated from a Git commit for a repo's file.
 | 
					// FileCommitResponse contains information generated from a Git commit for a repo's file.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -812,7 +812,8 @@ func GetContentsExt(ctx *context.APIContext) {
 | 
				
			|||||||
	//   required: true
 | 
						//   required: true
 | 
				
			||||||
	// - name: filepath
 | 
						// - name: filepath
 | 
				
			||||||
	//   in: path
 | 
						//   in: path
 | 
				
			||||||
	//   description: path of the dir, file, symlink or submodule in the repo
 | 
						//   description: path of the dir, file, symlink or submodule in the repo. Swagger requires path parameter to be "required",
 | 
				
			||||||
 | 
						//                you can leave it empty or pass a single dot (".") to get the root directory.
 | 
				
			||||||
	//   type: string
 | 
						//   type: string
 | 
				
			||||||
	//   required: true
 | 
						//   required: true
 | 
				
			||||||
	// - name: ref
 | 
						// - name: ref
 | 
				
			||||||
@@ -823,7 +824,8 @@ func GetContentsExt(ctx *context.APIContext) {
 | 
				
			|||||||
	// - name: includes
 | 
						// - name: includes
 | 
				
			||||||
	//   in: query
 | 
						//   in: query
 | 
				
			||||||
	//   description: By default this API's response only contains file's metadata. Use comma-separated "includes" options to retrieve more fields.
 | 
						//   description: By default this API's response only contains file's metadata. Use comma-separated "includes" options to retrieve more fields.
 | 
				
			||||||
	//                Option "file_content" will try to retrieve the file content, option "lfs_metadata" will try to retrieve LFS metadata.
 | 
						//                Option "file_content" will try to retrieve the file content, "lfs_metadata" will try to retrieve LFS metadata,
 | 
				
			||||||
 | 
						//                "commit_metadata" will try to retrieve commit metadata, and "commit_message" will try to retrieve commit message.
 | 
				
			||||||
	//   type: string
 | 
						//   type: string
 | 
				
			||||||
	//   required: false
 | 
						//   required: false
 | 
				
			||||||
	// responses:
 | 
						// responses:
 | 
				
			||||||
@@ -832,6 +834,9 @@ func GetContentsExt(ctx *context.APIContext) {
 | 
				
			|||||||
	//   "404":
 | 
						//   "404":
 | 
				
			||||||
	//     "$ref": "#/responses/notFound"
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if treePath := ctx.PathParam("*"); treePath == "." || treePath == "/" {
 | 
				
			||||||
 | 
							ctx.SetPathParam("*", "") // workaround for swagger, it requires path parameter to be "required", but we need to list root directory
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	opts := files_service.GetContentsOrListOptions{TreePath: ctx.PathParam("*")}
 | 
						opts := files_service.GetContentsOrListOptions{TreePath: ctx.PathParam("*")}
 | 
				
			||||||
	for includeOpt := range strings.SplitSeq(ctx.FormString("includes"), ",") {
 | 
						for includeOpt := range strings.SplitSeq(ctx.FormString("includes"), ",") {
 | 
				
			||||||
		if includeOpt == "" {
 | 
							if includeOpt == "" {
 | 
				
			||||||
@@ -842,6 +847,10 @@ func GetContentsExt(ctx *context.APIContext) {
 | 
				
			|||||||
			opts.IncludeSingleFileContent = true
 | 
								opts.IncludeSingleFileContent = true
 | 
				
			||||||
		case "lfs_metadata":
 | 
							case "lfs_metadata":
 | 
				
			||||||
			opts.IncludeLfsMetadata = true
 | 
								opts.IncludeLfsMetadata = true
 | 
				
			||||||
 | 
							case "commit_metadata":
 | 
				
			||||||
 | 
								opts.IncludeCommitMetadata = true
 | 
				
			||||||
 | 
							case "commit_message":
 | 
				
			||||||
 | 
								opts.IncludeCommitMessage = true
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			ctx.APIError(http.StatusBadRequest, fmt.Sprintf("unknown include option %q", includeOpt))
 | 
								ctx.APIError(http.StatusBadRequest, fmt.Sprintf("unknown include option %q", includeOpt))
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@@ -883,7 +892,11 @@ func GetContents(ctx *context.APIContext) {
 | 
				
			|||||||
	//     "$ref": "#/responses/ContentsResponse"
 | 
						//     "$ref": "#/responses/ContentsResponse"
 | 
				
			||||||
	//   "404":
 | 
						//   "404":
 | 
				
			||||||
	//     "$ref": "#/responses/notFound"
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
	ret := getRepoContents(ctx, files_service.GetContentsOrListOptions{TreePath: ctx.PathParam("*"), IncludeSingleFileContent: true})
 | 
						ret := getRepoContents(ctx, files_service.GetContentsOrListOptions{
 | 
				
			||||||
 | 
							TreePath:                 ctx.PathParam("*"),
 | 
				
			||||||
 | 
							IncludeSingleFileContent: true,
 | 
				
			||||||
 | 
							IncludeCommitMetadata:    true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	if ctx.Written() {
 | 
						if ctx.Written() {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,8 @@ type GetContentsOrListOptions struct {
 | 
				
			|||||||
	TreePath                 string
 | 
						TreePath                 string
 | 
				
			||||||
	IncludeSingleFileContent bool // include the file's content when the tree path is a file
 | 
						IncludeSingleFileContent bool // include the file's content when the tree path is a file
 | 
				
			||||||
	IncludeLfsMetadata       bool
 | 
						IncludeLfsMetadata       bool
 | 
				
			||||||
 | 
						IncludeCommitMetadata    bool
 | 
				
			||||||
 | 
						IncludeCommitMessage     bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetContentsOrList gets the metadata of a file's contents (*ContentsResponse) if treePath not a tree
 | 
					// GetContentsOrList gets the metadata of a file's contents (*ContentsResponse) if treePath not a tree
 | 
				
			||||||
@@ -132,6 +134,19 @@ func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Reposito
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	selfURLString := selfURL.String()
 | 
						selfURLString := selfURL.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// All content types have these fields in populated
 | 
				
			||||||
 | 
						contentsResponse := &api.ContentsResponse{
 | 
				
			||||||
 | 
							Name: entry.Name(),
 | 
				
			||||||
 | 
							Path: opts.TreePath,
 | 
				
			||||||
 | 
							SHA:  entry.ID.String(),
 | 
				
			||||||
 | 
							Size: entry.Size(),
 | 
				
			||||||
 | 
							URL:  &selfURLString,
 | 
				
			||||||
 | 
							Links: &api.FileLinksResponse{
 | 
				
			||||||
 | 
								Self: &selfURLString,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if opts.IncludeCommitMetadata || opts.IncludeCommitMessage {
 | 
				
			||||||
		err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
 | 
							err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
@@ -142,29 +157,23 @@ func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Reposito
 | 
				
			|||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// All content types have these fields in populated
 | 
							if opts.IncludeCommitMetadata {
 | 
				
			||||||
	contentsResponse := &api.ContentsResponse{
 | 
								contentsResponse.LastCommitSHA = util.ToPointer(lastCommit.ID.String())
 | 
				
			||||||
		Name:          entry.Name(),
 | 
					 | 
				
			||||||
		Path:          opts.TreePath,
 | 
					 | 
				
			||||||
		SHA:           entry.ID.String(),
 | 
					 | 
				
			||||||
		LastCommitSHA: lastCommit.ID.String(),
 | 
					 | 
				
			||||||
		Size:          entry.Size(),
 | 
					 | 
				
			||||||
		URL:           &selfURLString,
 | 
					 | 
				
			||||||
		Links: &api.FileLinksResponse{
 | 
					 | 
				
			||||||
			Self: &selfURLString,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// GitHub doesn't have these fields in the response, but we could follow other similar APIs to name them
 | 
								// GitHub doesn't have these fields in the response, but we could follow other similar APIs to name them
 | 
				
			||||||
			// https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
 | 
								// https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
 | 
				
			||||||
			if lastCommit.Committer != nil {
 | 
								if lastCommit.Committer != nil {
 | 
				
			||||||
		contentsResponse.LastCommitterDate = lastCommit.Committer.When
 | 
									contentsResponse.LastCommitterDate = util.ToPointer(lastCommit.Committer.When)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if lastCommit.Author != nil {
 | 
								if lastCommit.Author != nil {
 | 
				
			||||||
		contentsResponse.LastAuthorDate = lastCommit.Author.When
 | 
									contentsResponse.LastAuthorDate = util.ToPointer(lastCommit.Author.When)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if opts.IncludeCommitMessage {
 | 
				
			||||||
 | 
								contentsResponse.LastCommitMessage = util.ToPointer(lastCommit.Message())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Now populate the rest of the ContentsResponse based on entry type
 | 
						// Now populate the rest of the ContentsResponse based on the entry type
 | 
				
			||||||
	if entry.IsRegular() || entry.IsExecutable() {
 | 
						if entry.IsRegular() || entry.IsExecutable() {
 | 
				
			||||||
		contentsResponse.Type = string(ContentTypeRegular)
 | 
							contentsResponse.Type = string(ContentTypeRegular)
 | 
				
			||||||
		// if it is listing the repo root dir, don't waste system resources on reading content
 | 
							// if it is listing the repo root dir, don't waste system resources on reading content
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,56 +5,21 @@ package files
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/unittest"
 | 
						"code.gitea.io/gitea/models/unittest"
 | 
				
			||||||
	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"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/services/contexttest"
 | 
						"code.gitea.io/gitea/services/contexttest"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_ "code.gitea.io/gitea/models/actions"
 | 
						_ "code.gitea.io/gitea/models/actions"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
	"github.com/stretchr/testify/require"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMain(m *testing.M) {
 | 
					func TestMain(m *testing.M) {
 | 
				
			||||||
	unittest.MainTest(m)
 | 
						unittest.MainTest(m)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getExpectedReadmeContentsResponse() *api.ContentsResponse {
 | 
					 | 
				
			||||||
	treePath := "README.md"
 | 
					 | 
				
			||||||
	sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f"
 | 
					 | 
				
			||||||
	encoding := "base64"
 | 
					 | 
				
			||||||
	content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"
 | 
					 | 
				
			||||||
	selfURL := "https://try.gitea.io/api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
 | 
					 | 
				
			||||||
	htmlURL := "https://try.gitea.io/user2/repo1/src/branch/master/" + treePath
 | 
					 | 
				
			||||||
	gitURL := "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/" + sha
 | 
					 | 
				
			||||||
	downloadURL := "https://try.gitea.io/user2/repo1/raw/branch/master/" + treePath
 | 
					 | 
				
			||||||
	return &api.ContentsResponse{
 | 
					 | 
				
			||||||
		Name:              treePath,
 | 
					 | 
				
			||||||
		Path:              treePath,
 | 
					 | 
				
			||||||
		SHA:               "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
 | 
					 | 
				
			||||||
		LastCommitSHA:     "65f1bf27bc3bf70f64657658635e66094edbcb4d",
 | 
					 | 
				
			||||||
		LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
 | 
					 | 
				
			||||||
		LastAuthorDate:    time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
 | 
					 | 
				
			||||||
		Type:              "file",
 | 
					 | 
				
			||||||
		Size:              30,
 | 
					 | 
				
			||||||
		Encoding:          &encoding,
 | 
					 | 
				
			||||||
		Content:           &content,
 | 
					 | 
				
			||||||
		URL:               &selfURL,
 | 
					 | 
				
			||||||
		HTMLURL:           &htmlURL,
 | 
					 | 
				
			||||||
		GitURL:            &gitURL,
 | 
					 | 
				
			||||||
		DownloadURL:       &downloadURL,
 | 
					 | 
				
			||||||
		Links: &api.FileLinksResponse{
 | 
					 | 
				
			||||||
			Self:    &selfURL,
 | 
					 | 
				
			||||||
			GitURL:  &gitURL,
 | 
					 | 
				
			||||||
			HTMLURL: &htmlURL,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestGetContents(t *testing.T) {
 | 
					func TestGetContents(t *testing.T) {
 | 
				
			||||||
	unittest.PrepareTestEnv(t)
 | 
						unittest.PrepareTestEnv(t)
 | 
				
			||||||
	ctx, _ := contexttest.MockContext(t, "user2/repo1")
 | 
						ctx, _ := contexttest.MockContext(t, "user2/repo1")
 | 
				
			||||||
@@ -63,45 +28,8 @@ func TestGetContents(t *testing.T) {
 | 
				
			|||||||
	contexttest.LoadRepoCommit(t, ctx)
 | 
						contexttest.LoadRepoCommit(t, ctx)
 | 
				
			||||||
	contexttest.LoadUser(t, ctx, 2)
 | 
						contexttest.LoadUser(t, ctx, 2)
 | 
				
			||||||
	contexttest.LoadGitRepo(t, ctx)
 | 
						contexttest.LoadGitRepo(t, ctx)
 | 
				
			||||||
	defer ctx.Repo.GitRepo.Close()
 | 
					 | 
				
			||||||
	repo, gitRepo := ctx.Repo.Repository, ctx.Repo.GitRepo
 | 
					 | 
				
			||||||
	refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
 | 
					 | 
				
			||||||
	require.NoError(t, err)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("GetContentsOrList(README.md)-MetaOnly", func(t *testing.T) {
 | 
						// GetContentsOrList's behavior is fully tested in integration tests, so we don't need to test it here.
 | 
				
			||||||
		expectedContentsResponse := getExpectedReadmeContentsResponse()
 | 
					 | 
				
			||||||
		expectedContentsResponse.Encoding = nil // because will be in a list, doesn't have encoding and content
 | 
					 | 
				
			||||||
		expectedContentsResponse.Content = nil
 | 
					 | 
				
			||||||
		extResp, err := GetContentsOrList(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{TreePath: "README.md", IncludeSingleFileContent: false})
 | 
					 | 
				
			||||||
		assert.Equal(t, expectedContentsResponse, extResp.FileContents)
 | 
					 | 
				
			||||||
		assert.NoError(t, err)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("GetContentsOrList(README.md)", func(t *testing.T) {
 | 
					 | 
				
			||||||
		expectedContentsResponse := getExpectedReadmeContentsResponse()
 | 
					 | 
				
			||||||
		extResp, err := GetContentsOrList(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{TreePath: "README.md", IncludeSingleFileContent: true})
 | 
					 | 
				
			||||||
		assert.Equal(t, expectedContentsResponse, extResp.FileContents)
 | 
					 | 
				
			||||||
		assert.NoError(t, err)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("GetContentsOrList(RootDir)", func(t *testing.T) {
 | 
					 | 
				
			||||||
		readmeContentsResponse := getExpectedReadmeContentsResponse()
 | 
					 | 
				
			||||||
		readmeContentsResponse.Encoding = nil // because will be in a list, doesn't have encoding and content
 | 
					 | 
				
			||||||
		readmeContentsResponse.Content = nil
 | 
					 | 
				
			||||||
		expectedContentsListResponse := []*api.ContentsResponse{readmeContentsResponse}
 | 
					 | 
				
			||||||
		// even if IncludeFileContent is true, it has no effect for directory listing
 | 
					 | 
				
			||||||
		extResp, err := GetContentsOrList(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{TreePath: "", IncludeSingleFileContent: true})
 | 
					 | 
				
			||||||
		assert.Equal(t, expectedContentsListResponse, extResp.DirContents)
 | 
					 | 
				
			||||||
		assert.NoError(t, err)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("GetContentsOrList(NoSuchTreePath)", func(t *testing.T) {
 | 
					 | 
				
			||||||
		extResp, err := GetContentsOrList(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{TreePath: "no-such/file.md"})
 | 
					 | 
				
			||||||
		assert.Error(t, err)
 | 
					 | 
				
			||||||
		assert.EqualError(t, err, "object does not exist [id: , rel_path: no-such]")
 | 
					 | 
				
			||||||
		assert.Nil(t, extResp.DirContents)
 | 
					 | 
				
			||||||
		assert.Nil(t, extResp.FileContents)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("GetBlobBySHA", func(t *testing.T) {
 | 
						t.Run("GetBlobBySHA", func(t *testing.T) {
 | 
				
			||||||
		sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
 | 
							sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,12 @@ import (
 | 
				
			|||||||
func GetContentsListFromTreePaths(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treePaths []string) (files []*api.ContentsResponse) {
 | 
					func GetContentsListFromTreePaths(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, refCommit *utils.RefCommit, treePaths []string) (files []*api.ContentsResponse) {
 | 
				
			||||||
	var size int64
 | 
						var size int64
 | 
				
			||||||
	for _, treePath := range treePaths {
 | 
						for _, treePath := range treePaths {
 | 
				
			||||||
		fileContents, _ := GetFileContents(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{TreePath: treePath, IncludeSingleFileContent: true}) // ok if fails, then will be nil
 | 
							// ok if fails, then will be nil
 | 
				
			||||||
 | 
							fileContents, _ := GetFileContents(ctx, repo, gitRepo, refCommit, GetContentsOrListOptions{
 | 
				
			||||||
 | 
								TreePath:                 treePath,
 | 
				
			||||||
 | 
								IncludeSingleFileContent: true,
 | 
				
			||||||
 | 
								IncludeCommitMetadata:    true,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
		if fileContents != nil && fileContents.Content != nil && *fileContents.Content != "" {
 | 
							if fileContents != nil && fileContents.Content != nil && *fileContents.Content != "" {
 | 
				
			||||||
			// if content isn't empty (e.g., due to the single blob being too large), add file size to response size
 | 
								// if content isn't empty (e.g., due to the single blob being too large), add file size to response size
 | 
				
			||||||
			size += int64(len(*fileContents.Content))
 | 
								size += int64(len(*fileContents.Content))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							@@ -7547,7 +7547,7 @@
 | 
				
			|||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "description": "path of the dir, file, symlink or submodule in the repo",
 | 
					            "description": "path of the dir, file, symlink or submodule in the repo. Swagger requires path parameter to be \"required\", you can leave it empty or pass a single dot (\".\") to get the root directory.",
 | 
				
			||||||
            "name": "filepath",
 | 
					            "name": "filepath",
 | 
				
			||||||
            "in": "path",
 | 
					            "in": "path",
 | 
				
			||||||
            "required": true
 | 
					            "required": true
 | 
				
			||||||
@@ -7560,7 +7560,7 @@
 | 
				
			|||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "description": "By default this API's response only contains file's metadata. Use comma-separated \"includes\" options to retrieve more fields. Option \"file_content\" will try to retrieve the file content, option \"lfs_metadata\" will try to retrieve LFS metadata.",
 | 
					            "description": "By default this API's response only contains file's metadata. Use comma-separated \"includes\" options to retrieve more fields. Option \"file_content\" will try to retrieve the file content, \"lfs_metadata\" will try to retrieve LFS metadata, \"commit_metadata\" will try to retrieve commit metadata, and \"commit_message\" will try to retrieve commit message.",
 | 
				
			||||||
            "name": "includes",
 | 
					            "name": "includes",
 | 
				
			||||||
            "in": "query"
 | 
					            "in": "query"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@@ -22368,6 +22368,10 @@
 | 
				
			|||||||
          "format": "date-time",
 | 
					          "format": "date-time",
 | 
				
			||||||
          "x-go-name": "LastAuthorDate"
 | 
					          "x-go-name": "LastAuthorDate"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "last_commit_message": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "LastCommitMessage"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "last_commit_sha": {
 | 
					        "last_commit_sha": {
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "LastCommitSHA"
 | 
					          "x-go-name": "LastCommitSHA"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
						"code.gitea.io/gitea/modules/gitrepo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/services/context"
 | 
						"code.gitea.io/gitea/services/context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@@ -52,8 +53,8 @@ func getCreateFileOptions() api.CreateFileOptions {
 | 
				
			|||||||
func normalizeFileContentResponseCommitTime(c *api.ContentsResponse) {
 | 
					func normalizeFileContentResponseCommitTime(c *api.ContentsResponse) {
 | 
				
			||||||
	// decoded JSON response may contain different timezone from the one parsed by git commit
 | 
						// decoded JSON response may contain different timezone from the one parsed by git commit
 | 
				
			||||||
	// so we need to normalize the time to UTC to make "assert.Equal" pass
 | 
						// so we need to normalize the time to UTC to make "assert.Equal" pass
 | 
				
			||||||
	c.LastCommitterDate = c.LastCommitterDate.UTC()
 | 
						c.LastCommitterDate = util.ToPointer(c.LastCommitterDate.UTC())
 | 
				
			||||||
	c.LastAuthorDate = c.LastAuthorDate.UTC()
 | 
						c.LastAuthorDate = util.ToPointer(c.LastAuthorDate.UTC())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type apiFileResponseInfo struct {
 | 
					type apiFileResponseInfo struct {
 | 
				
			||||||
@@ -74,9 +75,9 @@ func getExpectedFileResponseForCreate(info apiFileResponseInfo) *api.FileRespons
 | 
				
			|||||||
			Name:              path.Base(info.treePath),
 | 
								Name:              path.Base(info.treePath),
 | 
				
			||||||
			Path:              info.treePath,
 | 
								Path:              info.treePath,
 | 
				
			||||||
			SHA:               sha,
 | 
								SHA:               sha,
 | 
				
			||||||
			LastCommitSHA:     info.lastCommitSHA,
 | 
								LastCommitSHA:     util.ToPointer(info.lastCommitSHA),
 | 
				
			||||||
			LastCommitterDate: info.lastCommitterWhen,
 | 
								LastCommitterDate: util.ToPointer(info.lastCommitterWhen),
 | 
				
			||||||
			LastAuthorDate:    info.lastAuthorWhen,
 | 
								LastAuthorDate:    util.ToPointer(info.lastAuthorWhen),
 | 
				
			||||||
			Size:              16,
 | 
								Size:              16,
 | 
				
			||||||
			Type:              "file",
 | 
								Type:              "file",
 | 
				
			||||||
			Encoding:          &encoding,
 | 
								Encoding:          &encoding,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
						"code.gitea.io/gitea/modules/gitrepo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/services/context"
 | 
						"code.gitea.io/gitea/services/context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@@ -60,9 +61,9 @@ func getExpectedFileResponseForUpdate(info apiFileResponseInfo) *api.FileRespons
 | 
				
			|||||||
			Name:              path.Base(info.treePath),
 | 
								Name:              path.Base(info.treePath),
 | 
				
			||||||
			Path:              info.treePath,
 | 
								Path:              info.treePath,
 | 
				
			||||||
			SHA:               sha,
 | 
								SHA:               sha,
 | 
				
			||||||
			LastCommitSHA:     info.lastCommitSHA,
 | 
								LastCommitSHA:     util.ToPointer(info.lastCommitSHA),
 | 
				
			||||||
			LastCommitterDate: info.lastCommitterWhen,
 | 
								LastCommitterDate: util.ToPointer(info.lastCommitterWhen),
 | 
				
			||||||
			LastAuthorDate:    info.lastAuthorWhen,
 | 
								LastAuthorDate:    util.ToPointer(info.lastAuthorWhen),
 | 
				
			||||||
			Type:              "file",
 | 
								Type:              "file",
 | 
				
			||||||
			Size:              20,
 | 
								Size:              20,
 | 
				
			||||||
			Encoding:          &encoding,
 | 
								Encoding:          &encoding,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
						"code.gitea.io/gitea/modules/gitrepo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	repo_service "code.gitea.io/gitea/services/repository"
 | 
						repo_service "code.gitea.io/gitea/services/repository"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@@ -35,9 +36,9 @@ func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA stri
 | 
				
			|||||||
			Name:              path.Base(treePath),
 | 
								Name:              path.Base(treePath),
 | 
				
			||||||
			Path:              treePath,
 | 
								Path:              treePath,
 | 
				
			||||||
			SHA:               sha,
 | 
								SHA:               sha,
 | 
				
			||||||
			LastCommitSHA:     lastCommitSHA,
 | 
								LastCommitSHA:     util.ToPointer(lastCommitSHA),
 | 
				
			||||||
			LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
 | 
								LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
 | 
				
			||||||
			LastAuthorDate:    time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
 | 
								LastAuthorDate:    util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
 | 
				
			||||||
			Type:              "file",
 | 
								Type:              "file",
 | 
				
			||||||
			Size:              30,
 | 
								Size:              30,
 | 
				
			||||||
			URL:               &selfURL,
 | 
								URL:               &selfURL,
 | 
				
			||||||
@@ -65,7 +66,6 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 | 
				
			|||||||
	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})   // public repo
 | 
						repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})   // public repo
 | 
				
			||||||
	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})   // public repo
 | 
						repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})   // public repo
 | 
				
			||||||
	repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
 | 
						repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
 | 
				
			||||||
	treePath := ""                                                                // root dir
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get user2's token
 | 
						// Get user2's token
 | 
				
			||||||
	session := loginUser(t, user2.Name)
 | 
						session := loginUser(t, user2.Name)
 | 
				
			||||||
@@ -94,7 +94,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 | 
				
			|||||||
	// ref is default ref
 | 
						// ref is default ref
 | 
				
			||||||
	ref := repo1.DefaultBranch
 | 
						ref := repo1.DefaultBranch
 | 
				
			||||||
	refType := "branch"
 | 
						refType := "branch"
 | 
				
			||||||
	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
 | 
						req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref)
 | 
				
			||||||
	resp := MakeRequest(t, req, http.StatusOK)
 | 
						resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	var contentsListResponse []*api.ContentsResponse
 | 
						var contentsListResponse []*api.ContentsResponse
 | 
				
			||||||
	DecodeJSON(t, resp, &contentsListResponse)
 | 
						DecodeJSON(t, resp, &contentsListResponse)
 | 
				
			||||||
@@ -106,7 +106,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// No ref
 | 
						// No ref
 | 
				
			||||||
	refType = "branch"
 | 
						refType = "branch"
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath)
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo1.Name)
 | 
				
			||||||
	resp = MakeRequest(t, req, http.StatusOK)
 | 
						resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	DecodeJSON(t, resp, &contentsListResponse)
 | 
						DecodeJSON(t, resp, &contentsListResponse)
 | 
				
			||||||
	assert.NotNil(t, contentsListResponse)
 | 
						assert.NotNil(t, contentsListResponse)
 | 
				
			||||||
@@ -117,7 +117,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 | 
				
			|||||||
	// ref is the branch we created above in setup
 | 
						// ref is the branch we created above in setup
 | 
				
			||||||
	ref = newBranch
 | 
						ref = newBranch
 | 
				
			||||||
	refType = "branch"
 | 
						refType = "branch"
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents?ref=%s", user2.Name, repo1.Name, ref)
 | 
				
			||||||
	resp = MakeRequest(t, req, http.StatusOK)
 | 
						resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	DecodeJSON(t, resp, &contentsListResponse)
 | 
						DecodeJSON(t, resp, &contentsListResponse)
 | 
				
			||||||
	assert.NotNil(t, contentsListResponse)
 | 
						assert.NotNil(t, contentsListResponse)
 | 
				
			||||||
@@ -131,7 +131,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 | 
				
			|||||||
	// ref is the new tag we created above in setup
 | 
						// ref is the new tag we created above in setup
 | 
				
			||||||
	ref = newTag
 | 
						ref = newTag
 | 
				
			||||||
	refType = "tag"
 | 
						refType = "tag"
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref)
 | 
				
			||||||
	resp = MakeRequest(t, req, http.StatusOK)
 | 
						resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	DecodeJSON(t, resp, &contentsListResponse)
 | 
						DecodeJSON(t, resp, &contentsListResponse)
 | 
				
			||||||
	assert.NotNil(t, contentsListResponse)
 | 
						assert.NotNil(t, contentsListResponse)
 | 
				
			||||||
@@ -145,7 +145,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 | 
				
			|||||||
	// ref is a commit
 | 
						// ref is a commit
 | 
				
			||||||
	ref = commitID
 | 
						ref = commitID
 | 
				
			||||||
	refType = "commit"
 | 
						refType = "commit"
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref)
 | 
				
			||||||
	resp = MakeRequest(t, req, http.StatusOK)
 | 
						resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	DecodeJSON(t, resp, &contentsListResponse)
 | 
						DecodeJSON(t, resp, &contentsListResponse)
 | 
				
			||||||
	assert.NotNil(t, contentsListResponse)
 | 
						assert.NotNil(t, contentsListResponse)
 | 
				
			||||||
@@ -154,21 +154,21 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Test file contents a file with a bad ref
 | 
						// Test file contents a file with a bad ref
 | 
				
			||||||
	ref = "badref"
 | 
						ref = "badref"
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/?ref=%s", user2.Name, repo1.Name, ref)
 | 
				
			||||||
	MakeRequest(t, req, http.StatusNotFound)
 | 
						MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test accessing private ref with user token that does not have access - should fail
 | 
						// Test accessing private ref with user token that does not have access - should fail
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath).
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name).
 | 
				
			||||||
		AddTokenAuth(token4)
 | 
							AddTokenAuth(token4)
 | 
				
			||||||
	MakeRequest(t, req, http.StatusNotFound)
 | 
						MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test access private ref of owner of token
 | 
						// Test access private ref of owner of token
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md", user2.Name, repo16.Name).
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", user2.Name, repo16.Name).
 | 
				
			||||||
		AddTokenAuth(token2)
 | 
							AddTokenAuth(token2)
 | 
				
			||||||
	MakeRequest(t, req, http.StatusOK)
 | 
						MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test access of org org3 private repo file by owner user2
 | 
						// Test access of org org3 private repo file by owner user2
 | 
				
			||||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath).
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/", org3.Name, repo3.Name).
 | 
				
			||||||
		AddTokenAuth(token2)
 | 
							AddTokenAuth(token2)
 | 
				
			||||||
	MakeRequest(t, req, http.StatusOK)
 | 
						MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,9 +35,9 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string)
 | 
				
			|||||||
		Name:              treePath,
 | 
							Name:              treePath,
 | 
				
			||||||
		Path:              treePath,
 | 
							Path:              treePath,
 | 
				
			||||||
		SHA:               "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
 | 
							SHA:               "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
 | 
				
			||||||
		LastCommitSHA:     lastCommitSHA,
 | 
							LastCommitSHA:     util.ToPointer(lastCommitSHA),
 | 
				
			||||||
		LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
 | 
							LastCommitterDate: util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
 | 
				
			||||||
		LastAuthorDate:    time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)),
 | 
							LastAuthorDate:    util.ToPointer(time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400))),
 | 
				
			||||||
		Type:              "file",
 | 
							Type:              "file",
 | 
				
			||||||
		Size:              30,
 | 
							Size:              30,
 | 
				
			||||||
		Encoding:          util.ToPointer("base64"),
 | 
							Encoding:          util.ToPointer("base64"),
 | 
				
			||||||
@@ -97,11 +97,16 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
 | 
				
			|||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
	/*** END SETUP ***/
 | 
						/*** END SETUP ***/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// not found
 | 
				
			||||||
 | 
						req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/no-such/file.md", user2.Name, repo1.Name)
 | 
				
			||||||
 | 
						resp := MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
						assert.Contains(t, resp.Body.String(), "object does not exist [id: , rel_path: no-such]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ref is default ref
 | 
						// ref is default ref
 | 
				
			||||||
	ref := repo1.DefaultBranch
 | 
						ref := repo1.DefaultBranch
 | 
				
			||||||
	refType := "branch"
 | 
						refType := "branch"
 | 
				
			||||||
	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
 | 
						req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref)
 | 
				
			||||||
	resp := MakeRequest(t, req, http.StatusOK)
 | 
						resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	var contentsResponse api.ContentsResponse
 | 
						var contentsResponse api.ContentsResponse
 | 
				
			||||||
	DecodeJSON(t, resp, &contentsResponse)
 | 
						DecodeJSON(t, resp, &contentsResponse)
 | 
				
			||||||
	lastCommit, _ := gitRepo.GetCommitByPath("README.md")
 | 
						lastCommit, _ := gitRepo.GetCommitByPath("README.md")
 | 
				
			||||||
@@ -206,14 +211,30 @@ func testAPIGetContentsExt(t *testing.T) {
 | 
				
			|||||||
	session := loginUser(t, "user2")
 | 
						session := loginUser(t, "user2")
 | 
				
			||||||
	token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 | 
						token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 | 
				
			||||||
	t.Run("DirContents", func(t *testing.T) {
 | 
						t.Run("DirContents", func(t *testing.T) {
 | 
				
			||||||
		req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check")
 | 
							req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext?ref=sub-home-md-img-check")
 | 
				
			||||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
							resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
		var contentsResponse api.ContentsExtResponse
 | 
							var contentsResponse api.ContentsExtResponse
 | 
				
			||||||
		DecodeJSON(t, resp, &contentsResponse)
 | 
							DecodeJSON(t, resp, &contentsResponse)
 | 
				
			||||||
		assert.Nil(t, contentsResponse.FileContents)
 | 
							assert.Nil(t, contentsResponse.FileContents)
 | 
				
			||||||
 | 
							assert.NotNil(t, contentsResponse.DirContents)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/.?ref=sub-home-md-img-check")
 | 
				
			||||||
 | 
							resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
							contentsResponse = api.ContentsExtResponse{}
 | 
				
			||||||
 | 
							DecodeJSON(t, resp, &contentsResponse)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.FileContents)
 | 
				
			||||||
 | 
							assert.NotNil(t, contentsResponse.DirContents)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check")
 | 
				
			||||||
 | 
							resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
							contentsResponse = api.ContentsExtResponse{}
 | 
				
			||||||
 | 
							DecodeJSON(t, resp, &contentsResponse)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.FileContents)
 | 
				
			||||||
		assert.Equal(t, "README.md", contentsResponse.DirContents[0].Name)
 | 
							assert.Equal(t, "README.md", contentsResponse.DirContents[0].Name)
 | 
				
			||||||
		assert.Nil(t, contentsResponse.DirContents[0].Encoding)
 | 
							assert.Nil(t, contentsResponse.DirContents[0].Encoding)
 | 
				
			||||||
		assert.Nil(t, contentsResponse.DirContents[0].Content)
 | 
							assert.Nil(t, contentsResponse.DirContents[0].Content)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.DirContents[0].LastCommitSHA)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.DirContents[0].LastCommitMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// "includes=file_content" shouldn't affect directory listing
 | 
							// "includes=file_content" shouldn't affect directory listing
 | 
				
			||||||
		req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check&includes=file_content")
 | 
							req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs?ref=sub-home-md-img-check&includes=file_content")
 | 
				
			||||||
@@ -240,7 +261,7 @@ func testAPIGetContentsExt(t *testing.T) {
 | 
				
			|||||||
		assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
 | 
							assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	t.Run("FileContents", func(t *testing.T) {
 | 
						t.Run("FileContents", func(t *testing.T) {
 | 
				
			||||||
		// by default, no file content is returned
 | 
							// by default, no file content or commit info is returned
 | 
				
			||||||
		req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check")
 | 
							req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check")
 | 
				
			||||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
							resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
		var contentsResponse api.ContentsExtResponse
 | 
							var contentsResponse api.ContentsExtResponse
 | 
				
			||||||
@@ -249,9 +270,11 @@ func testAPIGetContentsExt(t *testing.T) {
 | 
				
			|||||||
		assert.Equal(t, "README.md", contentsResponse.FileContents.Name)
 | 
							assert.Equal(t, "README.md", contentsResponse.FileContents.Name)
 | 
				
			||||||
		assert.Nil(t, contentsResponse.FileContents.Encoding)
 | 
							assert.Nil(t, contentsResponse.FileContents.Encoding)
 | 
				
			||||||
		assert.Nil(t, contentsResponse.FileContents.Content)
 | 
							assert.Nil(t, contentsResponse.FileContents.Content)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.FileContents.LastCommitSHA)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.FileContents.LastCommitMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// file content is only returned when `includes=file_content`
 | 
							// file content is only returned when `includes=file_content`
 | 
				
			||||||
		req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check&includes=file_content")
 | 
							req = NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/contents-ext/docs/README.md?ref=sub-home-md-img-check&includes=file_content,commit_metadata,commit_message")
 | 
				
			||||||
		resp = MakeRequest(t, req, http.StatusOK)
 | 
							resp = MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
		contentsResponse = api.ContentsExtResponse{}
 | 
							contentsResponse = api.ContentsExtResponse{}
 | 
				
			||||||
		DecodeJSON(t, resp, &contentsResponse)
 | 
							DecodeJSON(t, resp, &contentsResponse)
 | 
				
			||||||
@@ -259,6 +282,8 @@ func testAPIGetContentsExt(t *testing.T) {
 | 
				
			|||||||
		assert.Equal(t, "README.md", contentsResponse.FileContents.Name)
 | 
							assert.Equal(t, "README.md", contentsResponse.FileContents.Name)
 | 
				
			||||||
		assert.NotNil(t, contentsResponse.FileContents.Encoding)
 | 
							assert.NotNil(t, contentsResponse.FileContents.Encoding)
 | 
				
			||||||
		assert.NotNil(t, contentsResponse.FileContents.Content)
 | 
							assert.NotNil(t, contentsResponse.FileContents.Content)
 | 
				
			||||||
 | 
							assert.Equal(t, "4649299398e4d39a5c09eb4f534df6f1e1eb87cc", *contentsResponse.FileContents.LastCommitSHA)
 | 
				
			||||||
 | 
							assert.Equal(t, "Test how READMEs render images when found in a subfolder\n", *contentsResponse.FileContents.LastCommitMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		req = NewRequestf(t, "GET", "/api/v1/repos/user2/lfs/contents-ext/jpeg.jpg?includes=file_content").AddTokenAuth(token2)
 | 
							req = NewRequestf(t, "GET", "/api/v1/repos/user2/lfs/contents-ext/jpeg.jpg?includes=file_content").AddTokenAuth(token2)
 | 
				
			||||||
		resp = session.MakeRequest(t, req, http.StatusOK)
 | 
							resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
@@ -270,6 +295,8 @@ func testAPIGetContentsExt(t *testing.T) {
 | 
				
			|||||||
		assert.Equal(t, "jpeg.jpg", respFile.Name)
 | 
							assert.Equal(t, "jpeg.jpg", respFile.Name)
 | 
				
			||||||
		assert.NotNil(t, respFile.Encoding)
 | 
							assert.NotNil(t, respFile.Encoding)
 | 
				
			||||||
		assert.NotNil(t, respFile.Content)
 | 
							assert.NotNil(t, respFile.Content)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.FileContents.LastCommitSHA)
 | 
				
			||||||
 | 
							assert.Nil(t, contentsResponse.FileContents.LastCommitMessage)
 | 
				
			||||||
		assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize)
 | 
							assert.Equal(t, util.ToPointer(int64(107)), respFile.LfsSize)
 | 
				
			||||||
		assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
 | 
							assert.Equal(t, util.ToPointer("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -155,9 +155,9 @@ func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.
 | 
				
			|||||||
			Name:              path.Base(treePath),
 | 
								Name:              path.Base(treePath),
 | 
				
			||||||
			Path:              treePath,
 | 
								Path:              treePath,
 | 
				
			||||||
			SHA:               "103ff9234cefeee5ec5361d22b49fbb04d385885",
 | 
								SHA:               "103ff9234cefeee5ec5361d22b49fbb04d385885",
 | 
				
			||||||
			LastCommitSHA:     lastCommit.ID.String(),
 | 
								LastCommitSHA:     util.ToPointer(lastCommit.ID.String()),
 | 
				
			||||||
			LastCommitterDate: lastCommit.Committer.When,
 | 
								LastCommitterDate: util.ToPointer(lastCommit.Committer.When),
 | 
				
			||||||
			LastAuthorDate:    lastCommit.Author.When,
 | 
								LastAuthorDate:    util.ToPointer(lastCommit.Author.When),
 | 
				
			||||||
			Type:              "file",
 | 
								Type:              "file",
 | 
				
			||||||
			Size:              18,
 | 
								Size:              18,
 | 
				
			||||||
			Encoding:          &encoding,
 | 
								Encoding:          &encoding,
 | 
				
			||||||
@@ -198,7 +198,7 @@ func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.
 | 
				
			|||||||
					SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
 | 
										SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Message: "Updates README.md\n",
 | 
								Message: "Creates new/file.txt\n",
 | 
				
			||||||
			Tree: &api.CommitMeta{
 | 
								Tree: &api.CommitMeta{
 | 
				
			||||||
				URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
 | 
									URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc",
 | 
				
			||||||
				SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc",
 | 
									SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc",
 | 
				
			||||||
@@ -225,9 +225,9 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA
 | 
				
			|||||||
			Name:              filename,
 | 
								Name:              filename,
 | 
				
			||||||
			Path:              filename,
 | 
								Path:              filename,
 | 
				
			||||||
			SHA:               "dbf8d00e022e05b7e5cf7e535de857de57925647",
 | 
								SHA:               "dbf8d00e022e05b7e5cf7e535de857de57925647",
 | 
				
			||||||
			LastCommitSHA:     lastCommitSHA,
 | 
								LastCommitSHA:     util.ToPointer(lastCommitSHA),
 | 
				
			||||||
			LastCommitterDate: lastCommitterWhen,
 | 
								LastCommitterDate: util.ToPointer(lastCommitterWhen),
 | 
				
			||||||
			LastAuthorDate:    lastAuthorWhen,
 | 
								LastAuthorDate:    util.ToPointer(lastAuthorWhen),
 | 
				
			||||||
			Type:              "file",
 | 
								Type:              "file",
 | 
				
			||||||
			Size:              43,
 | 
								Size:              43,
 | 
				
			||||||
			Encoding:          &encoding,
 | 
								Encoding:          &encoding,
 | 
				
			||||||
@@ -331,7 +331,7 @@ func getExpectedFileResponseForRepoFilesUpdateRename(commitID, lastCommitSHA str
 | 
				
			|||||||
			Name:          detail.filename,
 | 
								Name:          detail.filename,
 | 
				
			||||||
			Path:          detail.filename,
 | 
								Path:          detail.filename,
 | 
				
			||||||
			SHA:           detail.sha,
 | 
								SHA:           detail.sha,
 | 
				
			||||||
			LastCommitSHA: lastCommitSHA,
 | 
								LastCommitSHA: util.ToPointer(lastCommitSHA),
 | 
				
			||||||
			Type:          "file",
 | 
								Type:          "file",
 | 
				
			||||||
			Size:          detail.size,
 | 
								Size:          detail.size,
 | 
				
			||||||
			Encoding:      util.ToPointer("base64"),
 | 
								Encoding:      util.ToPointer("base64"),
 | 
				
			||||||
@@ -537,7 +537,7 @@ func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) {
 | 
				
			|||||||
		lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
 | 
							lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath)
 | 
				
			||||||
		expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String())
 | 
							expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String())
 | 
				
			||||||
		for _, file := range filesResponse.Files {
 | 
							for _, file := range filesResponse.Files {
 | 
				
			||||||
			file.LastCommitterDate, file.LastAuthorDate = time.Time{}, time.Time{} // there might be different time in one operation, so we ignore them
 | 
								file.LastCommitterDate, file.LastAuthorDate = nil, nil // there might be different time in one operation, so we ignore them
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		assert.Len(t, filesResponse.Files, 4)
 | 
							assert.Len(t, filesResponse.Files, 4)
 | 
				
			||||||
		assert.Equal(t, expectedFileResponse.Files, filesResponse.Files)
 | 
							assert.Equal(t, expectedFileResponse.Files, filesResponse.Files)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user