From ec25a13a976de0f71318af1cd3e67e9543cb5790 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 1 Apr 2025 19:32:54 +0200 Subject: [PATCH] feat(api): add `last_commit_when` to contents response - Add a new field `last_commit_when` to the `ContentResponse` type, which is populated with the last commit's commiter date. This can be used to determine when the last edit of the content was. - This field is compatible with what Gitea will likely add, https://github.com/go-gitea/gitea/pull/32921. There's no field for this information in the Github API, so no way to be compatible with that (this API endpoint is otherwise fully compatible with Github's API). - Ref: gitnex/GitNex#1225 - Integration test adjusted. The API tests cannot test the actual output, as `testify` tries to 'deep equal' the `time.Time` structs which will differ due how the `time.Time` struct is created. Unit tests still verify the output. --- modules/structs/repo_file.go | 4 ++ services/repository/files/content.go | 13 ++-- services/repository/files/content_test.go | 26 ++++---- templates/swagger/v1_json.tmpl | 5 ++ .../integration/api_repo_file_create_test.go | 4 ++ .../integration/api_repo_file_update_test.go | 2 + .../integration/api_repo_files_change_test.go | 4 ++ .../api_repo_get_contents_list_test.go | 22 ++++--- .../integration/api_repo_get_contents_test.go | 26 ++++---- tests/integration/repofiles_change_test.go | 62 ++++++++++--------- 10 files changed, 98 insertions(+), 70 deletions(-) diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index 65d9a2d490..242343493b 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -4,6 +4,8 @@ package structs +import "time" + // FileOptions options for all file APIs type FileOptions struct { // message (optional) for the commit of this file. if not supplied, a default message will be used @@ -121,6 +123,8 @@ type ContentsResponse struct { Path string `json:"path"` SHA string `json:"sha"` LastCommitSHA string `json:"last_commit_sha"` + // swagger:strfmt date-time + LastCommitWhen time.Time `json:"last_commit_when"` // `type` will be `file`, `dir`, `symlink`, or `submodule` Type string `json:"type"` Size int64 `json:"size"` diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 3eb3049f12..5f7dd38303 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -178,12 +178,13 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref // All content types have these fields in populated contentsResponse := &api.ContentsResponse{ - Name: entry.Name(), - Path: treePath, - SHA: entry.ID.String(), - LastCommitSHA: lastCommit.ID.String(), - Size: entry.Size(), - URL: &selfURLString, + Name: entry.Name(), + Path: treePath, + SHA: entry.ID.String(), + LastCommitSHA: lastCommit.ID.String(), + LastCommitWhen: lastCommit.Committer.When, + Size: entry.Size(), + URL: &selfURLString, Links: &api.FileLinksResponse{ Self: &selfURLString, }, diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go index 78ad52c61c..e55b840660 100644 --- a/services/repository/files/content_test.go +++ b/services/repository/files/content_test.go @@ -5,6 +5,7 @@ package files import ( "testing" + "time" "forgejo.org/models/db" repo_model "forgejo.org/models/repo" @@ -33,18 +34,19 @@ func getExpectedReadmeContentsResponse() *api.ContentsResponse { 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", - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + LastCommitWhen: 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, diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 400c71af1a..906e98ec87 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -21833,6 +21833,11 @@ "type": "string", "x-go-name": "LastCommitSHA" }, + "last_commit_when": { + "type": "string", + "format": "date-time", + "x-go-name": "LastCommitWhen" + }, "name": { "type": "string", "x-go-name": "Name" diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 2cc05c42bb..4d34e5d43f 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -177,6 +177,8 @@ func TestAPICreateFile(t *testing.T) { expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath, latestCommit.ID.String()) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) + // Testify cannot assert time.Time correctly. + expectedFileResponse.Content.LastCommitWhen = fileResponse.Content.LastCommitWhen assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) @@ -296,6 +298,8 @@ func TestAPICreateFile(t *testing.T) { latestCommit, _ := gitRepo.GetCommitByPath(treePath) expectedFileResponse := getExpectedFileResponseForCreate("user2/"+reponame, commitID, treePath, latestCommit.ID.String()) DecodeJSON(t, resp, &fileResponse) + // Testify cannot assert time.Time correctly. + expectedFileResponse.Content.LastCommitWhen = fileResponse.Content.LastCommitWhen assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index b14dfbc565..098d7a3fde 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -140,6 +140,8 @@ func TestAPIUpdateFile(t *testing.T) { expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath, lasCommit.ID.String()) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) + // Testify cannot assert time.Time correctly. + expectedFileResponse.Content.LastCommitWhen = fileResponse.Content.LastCommitWhen assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) diff --git a/tests/integration/api_repo_files_change_test.go b/tests/integration/api_repo_files_change_test.go index 1272c3d8bf..1772dec6a6 100644 --- a/tests/integration/api_repo_files_change_test.go +++ b/tests/integration/api_repo_files_change_test.go @@ -104,6 +104,10 @@ func TestAPIChangeFiles(t *testing.T) { var filesResponse api.FilesResponse DecodeJSON(t, resp, &filesResponse) + // Testify cannot assert time.Time correctly. + expectedCreateFileResponse.Content.LastCommitWhen = filesResponse.Files[0].LastCommitWhen + expectedUpdateFileResponse.Content.LastCommitWhen = filesResponse.Files[1].LastCommitWhen + // check create file assert.Equal(t, expectedCreateFileResponse.Content, filesResponse.Files[0]) diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go index 2073c2cb98..7d010ffdf1 100644 --- a/tests/integration/api_repo_get_contents_list_test.go +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -8,6 +8,7 @@ import ( "net/url" "path/filepath" "testing" + "time" auth_model "forgejo.org/models/auth" repo_model "forgejo.org/models/repo" @@ -32,16 +33,17 @@ func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA stri downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath return []*api.ContentsResponse{ { - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 30, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: filepath.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + LastCommitWhen: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + Type: "file", + Size: 30, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index 4053828082..47ac4cfc03 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "testing" + "time" auth_model "forgejo.org/models/auth" repo_model "forgejo.org/models/repo" @@ -33,18 +34,19 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string) gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath return &api.ContentsResponse{ - Name: treePath, - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + LastCommitWhen: 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, diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go index 149b8b8d70..bac311f64e 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -108,7 +108,7 @@ func getExpectedFileResponseForRepofilesDelete() *api.FileResponse { } } -func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string, lastCommitWhen time.Time) *api.FileResponse { treePath := "new/file.txt" encoding := "base64" content := "VGhpcyBpcyBhIE5FVyBmaWxl" @@ -118,18 +118,19 @@ func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) * downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 18, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: filepath.Base(treePath), + Path: treePath, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + LastCommitSHA: lastCommitSHA, + LastCommitWhen: lastCommitWhen, + Type: "file", + Size: 18, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -177,7 +178,7 @@ func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) * } } -func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA string, lastCommitWhen time.Time) *api.FileResponse { encoding := "base64" content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ==" selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + filename + "?ref=master" @@ -186,18 +187,19 @@ func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + filename return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filename, - Path: filename, - SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 43, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: filename, + Path: filename, + SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", + LastCommitSHA: lastCommitSHA, + LastCommitWhen: lastCommitWhen, + Type: "file", + Size: 43, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -264,7 +266,7 @@ func TestChangeRepoFiles(t *testing.T) { require.NoError(t, err) lastCommit, err := gitRepo.GetCommitByPath("new/file.txt") require.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID, lastCommit.ID.String(), lastCommit.Committer.When) assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) @@ -282,7 +284,7 @@ func TestChangeRepoFiles(t *testing.T) { require.NoError(t, err) lastCommit, err := commit.GetCommitByPath(opts.Files[0].TreePath) require.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When) assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) @@ -303,7 +305,7 @@ func TestChangeRepoFiles(t *testing.T) { require.NoError(t, err) lastCommit, err := commit.GetCommitByPath(opts.Files[0].TreePath) require.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When) // assert that the old file no longer exists in the last commit of the branch fromEntry, err := commit.GetTreeEntryByPath(opts.Files[0].FromTreePath) @@ -339,7 +341,7 @@ func TestChangeRepoFiles(t *testing.T) { commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch) lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When) assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) })