mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 03:02:14 +01: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:
		
							parent
							
								
									97fc87af89
								
							
						
					
					
						commit
						6455c8202b
					
				| @ -116,14 +116,17 @@ type ContentsExtResponse struct { | ||||
| 
 | ||||
| // ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content | ||||
| type ContentsResponse struct { | ||||
| 	Name          string `json:"name"` | ||||
| 	Path          string `json:"path"` | ||||
| 	SHA           string `json:"sha"` | ||||
| 	LastCommitSHA string `json:"last_commit_sha"` | ||||
| 	Name string `json:"name"` | ||||
| 	Path string `json:"path"` | ||||
| 	SHA  string `json:"sha"` | ||||
| 
 | ||||
| 	LastCommitSHA *string `json:"last_commit_sha,omitempty"` | ||||
| 	// swagger:strfmt date-time | ||||
| 	LastCommitterDate time.Time `json:"last_committer_date"` | ||||
| 	LastCommitterDate *time.Time `json:"last_committer_date,omitempty"` | ||||
| 	// 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 string `json:"type"` | ||||
| 	Size int64  `json:"size"` | ||||
| @ -141,8 +144,8 @@ type ContentsResponse struct { | ||||
| 	SubmoduleGitURL *string            `json:"submodule_git_url"` | ||||
| 	Links           *FileLinksResponse `json:"_links"` | ||||
| 
 | ||||
| 	LfsOid  *string `json:"lfs_oid"` | ||||
| 	LfsSize *int64  `json:"lfs_size"` | ||||
| 	LfsOid  *string `json:"lfs_oid,omitempty"` | ||||
| 	LfsSize *int64  `json:"lfs_size,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // FileCommitResponse contains information generated from a Git commit for a repo's file. | ||||
|  | ||||
| @ -812,7 +812,8 @@ func GetContentsExt(ctx *context.APIContext) { | ||||
| 	//   required: true | ||||
| 	// - name: filepath | ||||
| 	//   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 | ||||
| 	//   required: true | ||||
| 	// - name: ref | ||||
| @ -823,7 +824,8 @@ func GetContentsExt(ctx *context.APIContext) { | ||||
| 	// - name: includes | ||||
| 	//   in: query | ||||
| 	//   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 | ||||
| 	//   required: false | ||||
| 	// responses: | ||||
| @ -832,6 +834,9 @@ func GetContentsExt(ctx *context.APIContext) { | ||||
| 	//   "404": | ||||
| 	//     "$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("*")} | ||||
| 	for includeOpt := range strings.SplitSeq(ctx.FormString("includes"), ",") { | ||||
| 		if includeOpt == "" { | ||||
| @ -842,6 +847,10 @@ func GetContentsExt(ctx *context.APIContext) { | ||||
| 			opts.IncludeSingleFileContent = true | ||||
| 		case "lfs_metadata": | ||||
| 			opts.IncludeLfsMetadata = true | ||||
| 		case "commit_metadata": | ||||
| 			opts.IncludeCommitMetadata = true | ||||
| 		case "commit_message": | ||||
| 			opts.IncludeCommitMessage = true | ||||
| 		default: | ||||
| 			ctx.APIError(http.StatusBadRequest, fmt.Sprintf("unknown include option %q", includeOpt)) | ||||
| 			return | ||||
| @ -883,7 +892,11 @@ func GetContents(ctx *context.APIContext) { | ||||
| 	//     "$ref": "#/responses/ContentsResponse" | ||||
| 	//   "404": | ||||
| 	//     "$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() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| @ -39,6 +39,8 @@ type GetContentsOrListOptions struct { | ||||
| 	TreePath                 string | ||||
| 	IncludeSingleFileContent bool // include the file's content when the tree path is a file | ||||
| 	IncludeLfsMetadata       bool | ||||
| 	IncludeCommitMetadata    bool | ||||
| 	IncludeCommitMessage     bool | ||||
| } | ||||
| 
 | ||||
| // GetContentsOrList gets the metadata of a file's contents (*ContentsResponse) if treePath not a tree | ||||
| @ -132,39 +134,46 @@ func getFileContentsByEntryInternal(_ context.Context, repo *repo_model.Reposito | ||||
| 	} | ||||
| 	selfURLString := selfURL.String() | ||||
| 
 | ||||
| 	err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	lastCommit, err := refCommit.Commit.GetCommitByPath(opts.TreePath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// All content types have these fields in populated | ||||
| 	contentsResponse := &api.ContentsResponse{ | ||||
| 		Name:          entry.Name(), | ||||
| 		Path:          opts.TreePath, | ||||
| 		SHA:           entry.ID.String(), | ||||
| 		LastCommitSHA: lastCommit.ID.String(), | ||||
| 		Size:          entry.Size(), | ||||
| 		URL:           &selfURLString, | ||||
| 		Name: entry.Name(), | ||||
| 		Path: opts.TreePath, | ||||
| 		SHA:  entry.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 | ||||
| 	// https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits | ||||
| 	if lastCommit.Committer != nil { | ||||
| 		contentsResponse.LastCommitterDate = lastCommit.Committer.When | ||||
| 	} | ||||
| 	if lastCommit.Author != nil { | ||||
| 		contentsResponse.LastAuthorDate = lastCommit.Author.When | ||||
| 	if opts.IncludeCommitMetadata || opts.IncludeCommitMessage { | ||||
| 		err = gitRepo.AddLastCommitCache(repo.GetCommitsCountCacheKey(refCommit.InputRef, refType != git.RefTypeCommit), repo.FullName(), refCommit.CommitID) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		lastCommit, err := refCommit.Commit.GetCommitByPath(opts.TreePath) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		if opts.IncludeCommitMetadata { | ||||
| 			contentsResponse.LastCommitSHA = util.ToPointer(lastCommit.ID.String()) | ||||
| 			// 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 | ||||
| 			if lastCommit.Committer != nil { | ||||
| 				contentsResponse.LastCommitterDate = util.ToPointer(lastCommit.Committer.When) | ||||
| 			} | ||||
| 			if lastCommit.Author != nil { | ||||
| 				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() { | ||||
| 		contentsResponse.Type = string(ContentTypeRegular) | ||||
| 		// if it is listing the repo root dir, don't waste system resources on reading content | ||||
|  | ||||
| @ -5,56 +5,21 @@ package files | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	"code.gitea.io/gitea/routers/api/v1/utils" | ||||
| 	"code.gitea.io/gitea/services/contexttest" | ||||
| 
 | ||||
| 	_ "code.gitea.io/gitea/models/actions" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
| 
 | ||||
| func TestMain(m *testing.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) { | ||||
| 	unittest.PrepareTestEnv(t) | ||||
| 	ctx, _ := contexttest.MockContext(t, "user2/repo1") | ||||
| @ -63,45 +28,8 @@ func TestGetContents(t *testing.T) { | ||||
| 	contexttest.LoadRepoCommit(t, ctx) | ||||
| 	contexttest.LoadUser(t, ctx, 2) | ||||
| 	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) { | ||||
| 		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) | ||||
| 	}) | ||||
| 	// GetContentsOrList's behavior is fully tested in integration tests, so we don't need to test it here. | ||||
| 
 | ||||
| 	t.Run("GetBlobBySHA", func(t *testing.T) { | ||||
| 		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) { | ||||
| 	var size int64 | ||||
| 	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 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)) | ||||
|  | ||||
							
								
								
									
										8
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							| @ -7547,7 +7547,7 @@ | ||||
|           }, | ||||
|           { | ||||
|             "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", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
| @ -7560,7 +7560,7 @@ | ||||
|           }, | ||||
|           { | ||||
|             "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", | ||||
|             "in": "query" | ||||
|           } | ||||
| @ -22368,6 +22368,10 @@ | ||||
|           "format": "date-time", | ||||
|           "x-go-name": "LastAuthorDate" | ||||
|         }, | ||||
|         "last_commit_message": { | ||||
|           "type": "string", | ||||
|           "x-go-name": "LastCommitMessage" | ||||
|         }, | ||||
|         "last_commit_sha": { | ||||
|           "type": "string", | ||||
|           "x-go-name": "LastCommitSHA" | ||||
|  | ||||
| @ -19,6 +19,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/gitrepo" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	"code.gitea.io/gitea/services/context" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| @ -52,8 +53,8 @@ func getCreateFileOptions() api.CreateFileOptions { | ||||
| func normalizeFileContentResponseCommitTime(c *api.ContentsResponse) { | ||||
| 	// 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 | ||||
| 	c.LastCommitterDate = c.LastCommitterDate.UTC() | ||||
| 	c.LastAuthorDate = c.LastAuthorDate.UTC() | ||||
| 	c.LastCommitterDate = util.ToPointer(c.LastCommitterDate.UTC()) | ||||
| 	c.LastAuthorDate = util.ToPointer(c.LastAuthorDate.UTC()) | ||||
| } | ||||
| 
 | ||||
| type apiFileResponseInfo struct { | ||||
| @ -74,9 +75,9 @@ func getExpectedFileResponseForCreate(info apiFileResponseInfo) *api.FileRespons | ||||
| 			Name:              path.Base(info.treePath), | ||||
| 			Path:              info.treePath, | ||||
| 			SHA:               sha, | ||||
| 			LastCommitSHA:     info.lastCommitSHA, | ||||
| 			LastCommitterDate: info.lastCommitterWhen, | ||||
| 			LastAuthorDate:    info.lastAuthorWhen, | ||||
| 			LastCommitSHA:     util.ToPointer(info.lastCommitSHA), | ||||
| 			LastCommitterDate: util.ToPointer(info.lastCommitterWhen), | ||||
| 			LastAuthorDate:    util.ToPointer(info.lastAuthorWhen), | ||||
| 			Size:              16, | ||||
| 			Type:              "file", | ||||
| 			Encoding:          &encoding, | ||||
|  | ||||
| @ -18,6 +18,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/gitrepo" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	"code.gitea.io/gitea/services/context" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| @ -60,9 +61,9 @@ func getExpectedFileResponseForUpdate(info apiFileResponseInfo) *api.FileRespons | ||||
| 			Name:              path.Base(info.treePath), | ||||
| 			Path:              info.treePath, | ||||
| 			SHA:               sha, | ||||
| 			LastCommitSHA:     info.lastCommitSHA, | ||||
| 			LastCommitterDate: info.lastCommitterWhen, | ||||
| 			LastAuthorDate:    info.lastAuthorWhen, | ||||
| 			LastCommitSHA:     util.ToPointer(info.lastCommitSHA), | ||||
| 			LastCommitterDate: util.ToPointer(info.lastCommitterWhen), | ||||
| 			LastAuthorDate:    util.ToPointer(info.lastAuthorWhen), | ||||
| 			Type:              "file", | ||||
| 			Size:              20, | ||||
| 			Encoding:          &encoding, | ||||
|  | ||||
| @ -18,6 +18,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/gitrepo" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	repo_service "code.gitea.io/gitea/services/repository" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| @ -35,9 +36,9 @@ func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA stri | ||||
| 			Name:              path.Base(treePath), | ||||
| 			Path:              treePath, | ||||
| 			SHA:               sha, | ||||
| 			LastCommitSHA:     lastCommitSHA, | ||||
| 			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)), | ||||
| 			LastCommitSHA:     util.ToPointer(lastCommitSHA), | ||||
| 			LastCommitterDate: util.ToPointer(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", | ||||
| 			Size:              30, | ||||
| 			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 | ||||
| 	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})   // public repo | ||||
| 	repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo | ||||
| 	treePath := ""                                                                // root dir | ||||
| 
 | ||||
| 	// Get user2's token | ||||
| 	session := loginUser(t, user2.Name) | ||||
| @ -94,7 +94,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { | ||||
| 	// ref is default ref | ||||
| 	ref := repo1.DefaultBranch | ||||
| 	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) | ||||
| 	var contentsListResponse []*api.ContentsResponse | ||||
| 	DecodeJSON(t, resp, &contentsListResponse) | ||||
| @ -106,7 +106,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { | ||||
| 
 | ||||
| 	// No ref | ||||
| 	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) | ||||
| 	DecodeJSON(t, resp, &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 = newBranch | ||||
| 	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) | ||||
| 	DecodeJSON(t, resp, &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 = newTag | ||||
| 	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) | ||||
| 	DecodeJSON(t, resp, &contentsListResponse) | ||||
| 	assert.NotNil(t, contentsListResponse) | ||||
| @ -145,7 +145,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { | ||||
| 	// ref is a commit | ||||
| 	ref = commitID | ||||
| 	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) | ||||
| 	DecodeJSON(t, resp, &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 | ||||
| 	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) | ||||
| 
 | ||||
| 	// 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) | ||||
| 	MakeRequest(t, req, http.StatusNotFound) | ||||
| 
 | ||||
| 	// 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) | ||||
| 	MakeRequest(t, req, http.StatusOK) | ||||
| 
 | ||||
| 	// 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) | ||||
| 	MakeRequest(t, req, http.StatusOK) | ||||
| } | ||||
|  | ||||
| @ -35,9 +35,9 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string) | ||||
| 		Name:              treePath, | ||||
| 		Path:              treePath, | ||||
| 		SHA:               "4b4851ad51df6a7d9f25c979345979eaeb5b349f", | ||||
| 		LastCommitSHA:     lastCommitSHA, | ||||
| 		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)), | ||||
| 		LastCommitSHA:     util.ToPointer(lastCommitSHA), | ||||
| 		LastCommitterDate: util.ToPointer(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", | ||||
| 		Size:              30, | ||||
| 		Encoding:          util.ToPointer("base64"), | ||||
| @ -97,11 +97,16 @@ func testAPIGetContents(t *testing.T, u *url.URL) { | ||||
| 	require.NoError(t, err) | ||||
| 	/*** 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 := repo1.DefaultBranch | ||||
| 	refType := "branch" | ||||
| 	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) | ||||
| 	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) | ||||
| 	var contentsResponse api.ContentsResponse | ||||
| 	DecodeJSON(t, resp, &contentsResponse) | ||||
| 	lastCommit, _ := gitRepo.GetCommitByPath("README.md") | ||||
| @ -116,7 +121,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { | ||||
| 	expectedContentsResponse = getExpectedContentsResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) | ||||
| 	assert.Equal(t, *expectedContentsResponse, contentsResponse) | ||||
| 
 | ||||
| 	// ref is the branch we created above  in setup | ||||
| 	// ref is the branch we created above in setup | ||||
| 	ref = newBranch | ||||
| 	refType = "branch" | ||||
| 	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | ||||
| @ -206,14 +211,30 @@ func testAPIGetContentsExt(t *testing.T) { | ||||
| 	session := loginUser(t, "user2") | ||||
| 	token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | ||||
| 	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) | ||||
| 		var 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/.?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.Nil(t, contentsResponse.DirContents[0].Encoding) | ||||
| 		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 | ||||
| 		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) | ||||
| 	}) | ||||
| 	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") | ||||
| 		resp := MakeRequest(t, req, http.StatusOK) | ||||
| 		var contentsResponse api.ContentsExtResponse | ||||
| @ -249,9 +270,11 @@ func testAPIGetContentsExt(t *testing.T) { | ||||
| 		assert.Equal(t, "README.md", contentsResponse.FileContents.Name) | ||||
| 		assert.Nil(t, contentsResponse.FileContents.Encoding) | ||||
| 		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` | ||||
| 		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) | ||||
| 		contentsResponse = api.ContentsExtResponse{} | ||||
| 		DecodeJSON(t, resp, &contentsResponse) | ||||
| @ -259,6 +282,8 @@ func testAPIGetContentsExt(t *testing.T) { | ||||
| 		assert.Equal(t, "README.md", contentsResponse.FileContents.Name) | ||||
| 		assert.NotNil(t, contentsResponse.FileContents.Encoding) | ||||
| 		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) | ||||
| 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||
| @ -270,6 +295,8 @@ func testAPIGetContentsExt(t *testing.T) { | ||||
| 		assert.Equal(t, "jpeg.jpg", respFile.Name) | ||||
| 		assert.NotNil(t, respFile.Encoding) | ||||
| 		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("0b8d8b5f15046343fd32f451df93acc2bdd9e6373be478b968e4cad6b6647351"), respFile.LfsOid) | ||||
| 	}) | ||||
|  | ||||
| @ -155,9 +155,9 @@ func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git. | ||||
| 			Name:              path.Base(treePath), | ||||
| 			Path:              treePath, | ||||
| 			SHA:               "103ff9234cefeee5ec5361d22b49fbb04d385885", | ||||
| 			LastCommitSHA:     lastCommit.ID.String(), | ||||
| 			LastCommitterDate: lastCommit.Committer.When, | ||||
| 			LastAuthorDate:    lastCommit.Author.When, | ||||
| 			LastCommitSHA:     util.ToPointer(lastCommit.ID.String()), | ||||
| 			LastCommitterDate: util.ToPointer(lastCommit.Committer.When), | ||||
| 			LastAuthorDate:    util.ToPointer(lastCommit.Author.When), | ||||
| 			Type:              "file", | ||||
| 			Size:              18, | ||||
| 			Encoding:          &encoding, | ||||
| @ -198,7 +198,7 @@ func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git. | ||||
| 					SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", | ||||
| 				}, | ||||
| 			}, | ||||
| 			Message: "Updates README.md\n", | ||||
| 			Message: "Creates new/file.txt\n", | ||||
| 			Tree: &api.CommitMeta{ | ||||
| 				URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", | ||||
| 				SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc", | ||||
| @ -225,9 +225,9 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA | ||||
| 			Name:              filename, | ||||
| 			Path:              filename, | ||||
| 			SHA:               "dbf8d00e022e05b7e5cf7e535de857de57925647", | ||||
| 			LastCommitSHA:     lastCommitSHA, | ||||
| 			LastCommitterDate: lastCommitterWhen, | ||||
| 			LastAuthorDate:    lastAuthorWhen, | ||||
| 			LastCommitSHA:     util.ToPointer(lastCommitSHA), | ||||
| 			LastCommitterDate: util.ToPointer(lastCommitterWhen), | ||||
| 			LastAuthorDate:    util.ToPointer(lastAuthorWhen), | ||||
| 			Type:              "file", | ||||
| 			Size:              43, | ||||
| 			Encoding:          &encoding, | ||||
| @ -331,7 +331,7 @@ func getExpectedFileResponseForRepoFilesUpdateRename(commitID, lastCommitSHA str | ||||
| 			Name:          detail.filename, | ||||
| 			Path:          detail.filename, | ||||
| 			SHA:           detail.sha, | ||||
| 			LastCommitSHA: lastCommitSHA, | ||||
| 			LastCommitSHA: util.ToPointer(lastCommitSHA), | ||||
| 			Type:          "file", | ||||
| 			Size:          detail.size, | ||||
| 			Encoding:      util.ToPointer("base64"), | ||||
| @ -537,7 +537,7 @@ func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) { | ||||
| 		lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) | ||||
| 		expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String()) | ||||
| 		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.Equal(t, expectedFileResponse.Files, filesResponse.Files) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user