loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request '[gitea] week 2024-45 cherry pick (gitea/main -> forgejo)' (#5789) from algernon/wcp/2024-45 into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5789
Reviewed-by: Gusted <gusted@noreply.codeberg.org>

+349 -163
-4
build/generate-emoji.go
··· 53 53 } 54 54 55 55 func main() { 56 - var err error 57 - 58 56 flag.Parse() 59 57 60 58 // generate data ··· 83 81 var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`) 84 82 85 83 func generate() ([]byte, error) { 86 - var err error 87 - 88 84 // load gemoji data 89 85 res, err := http.Get(gemojiURL) 90 86 if err != nil {
+1 -1
cmd/migrate_storage_test.go
··· 91 91 92 92 srcStorage, _ := createLocalStorage(t) 93 93 defer test.MockVariableValue(&storage.ActionsArtifacts, srcStorage)() 94 - id := int64(0) 94 + id := int64(42) 95 95 96 96 addArtifact := func(storagePath string, status actions.ArtifactStatus) { 97 97 id++
+8
custom/conf/app.example.ini
··· 328 328 ;; Maximum number of locks returned per page 329 329 ;LFS_LOCKS_PAGING_NUM = 50 330 330 ;; 331 + ;; When clients make lfs batch requests, reject them if there are more pointers than this number 332 + ;; zero means 'unlimited' 333 + ;LFS_MAX_BATCH_SIZE = 0 334 + ;; 331 335 ;; Allow graceful restarts using SIGHUP to fork 332 336 ;ALLOW_GRACEFUL_RESTARTS = true 333 337 ;; ··· 2671 2675 ;; 2672 2676 ;; override the minio base path if storage type is minio 2673 2677 ;MINIO_BASE_PATH = lfs/ 2678 + 2679 + ;[lfs_client] 2680 + ;; When mirroring an upstream lfs endpoint, limit the number of pointers in each batch request to this number 2681 + ;BATCH_SIZE = 20 2674 2682 2675 2683 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2676 2684 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+71
models/fixtures/action_artifact.yml
··· 1 + - 2 + id: 1 3 + run_id: 791 4 + runner_id: 1 5 + repo_id: 4 6 + owner_id: 1 7 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 8 + storage_path: "26/1/1712166500347189545.chunk" 9 + file_size: 1024 10 + file_compressed_size: 1024 11 + content_encoding: "" 12 + artifact_path: "abc.txt" 13 + artifact_name: "artifact-download" 14 + status: 1 15 + created_unix: 1712338649 16 + updated_unix: 1712338649 17 + expired_unix: 1720114649 18 + 19 + - 20 + id: 19 21 + run_id: 791 22 + runner_id: 1 23 + repo_id: 4 24 + owner_id: 1 25 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 26 + storage_path: "26/19/1712348022422036662.chunk" 27 + file_size: 1024 28 + file_compressed_size: 1024 29 + content_encoding: "" 30 + artifact_path: "abc.txt" 31 + artifact_name: "multi-file-download" 32 + status: 2 33 + created_unix: 1712348022 34 + updated_unix: 1712348022 35 + expired_unix: 1720124022 36 + 37 + - 38 + id: 20 39 + run_id: 791 40 + runner_id: 1 41 + repo_id: 4 42 + owner_id: 1 43 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 44 + storage_path: "26/20/1712348022423431524.chunk" 45 + file_size: 1024 46 + file_compressed_size: 1024 47 + content_encoding: "" 48 + artifact_path: "xyz/def.txt" 49 + artifact_name: "multi-file-download" 50 + status: 2 51 + created_unix: 1712348022 52 + updated_unix: 1712348022 53 + expired_unix: 1720124022 54 + 55 + - 56 + id: 22 57 + run_id: 792 58 + runner_id: 1 59 + repo_id: 4 60 + owner_id: 1 61 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 62 + storage_path: "27/5/1730330775594233150.chunk" 63 + file_size: 1024 64 + file_compressed_size: 1024 65 + content_encoding: "application/zip" 66 + artifact_path: "artifact-v4-download.zip" 67 + artifact_name: "artifact-v4-download" 68 + status: 2 69 + created_unix: 1730330775 70 + updated_unix: 1730330775 71 + expired_unix: 1738106775
-2
models/git/lfs.go
··· 136 136 // NewLFSMetaObject stores a given populated LFSMetaObject structure in the database 137 137 // if it is not already present. 138 138 func NewLFSMetaObject(ctx context.Context, repoID int64, p lfs.Pointer) (*LFSMetaObject, error) { 139 - var err error 140 - 141 139 ctx, committer, err := db.TxContext(ctx) 142 140 if err != nil { 143 141 return nil, err
+29
models/git/protected_banch_list_test.go models/git/protected_branch_list_test.go
··· 75 75 } 76 76 } 77 77 } 78 + 79 + func TestBranchRuleSort(t *testing.T) { 80 + in := []*ProtectedBranch{{ 81 + RuleName: "b", 82 + CreatedUnix: 1, 83 + }, { 84 + RuleName: "b/*", 85 + CreatedUnix: 3, 86 + }, { 87 + RuleName: "a/*", 88 + CreatedUnix: 2, 89 + }, { 90 + RuleName: "c", 91 + CreatedUnix: 0, 92 + }, { 93 + RuleName: "a", 94 + CreatedUnix: 4, 95 + }} 96 + expect := []string{"c", "b", "a", "a/*", "b/*"} 97 + 98 + pbr := ProtectedBranchRules(in) 99 + pbr.sort() 100 + 101 + var got []string 102 + for i := range pbr { 103 + got = append(got, pbr[i].RuleName) 104 + } 105 + assert.Equal(t, expect, got) 106 + }
+14 -8
models/git/protected_branch.go
··· 79 79 } 80 80 81 81 func (protectBranch *ProtectedBranch) loadGlob() { 82 - if protectBranch.globRule == nil { 83 - var err error 84 - protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/') 85 - if err != nil { 86 - log.Warn("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err) 87 - protectBranch.globRule = glob.MustCompile(glob.QuoteMeta(protectBranch.RuleName), '/') 88 - } 89 - protectBranch.isPlainName = !IsRuleNameSpecial(protectBranch.RuleName) 82 + if protectBranch.isPlainName || protectBranch.globRule != nil { 83 + return 84 + } 85 + // detect if it is not glob 86 + if !IsRuleNameSpecial(protectBranch.RuleName) { 87 + protectBranch.isPlainName = true 88 + return 89 + } 90 + // now we load the glob 91 + var err error 92 + protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/') 93 + if err != nil { 94 + log.Warn("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err) 95 + protectBranch.globRule = glob.MustCompile(glob.QuoteMeta(protectBranch.RuleName), '/') 90 96 } 91 97 } 92 98
+1 -2
models/issues/label_test.go
··· 231 231 testSuccess(3, "reversealphabetically", []int64{4, 3}) 232 232 testSuccess(3, "default", []int64{3, 4}) 233 233 234 - var err error 235 - _, err = issues_model.GetLabelsByOrgID(db.DefaultContext, 0, "leastissues", db.ListOptions{}) 234 + _, err := issues_model.GetLabelsByOrgID(db.DefaultContext, 0, "leastissues", db.ListOptions{}) 236 235 assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) 237 236 238 237 _, err = issues_model.GetLabelsByOrgID(db.DefaultContext, -1, "leastissues", db.ListOptions{})
+1 -3
modules/charset/charset_test.go
··· 41 41 42 42 func TestToUTF8(t *testing.T) { 43 43 resetDefaultCharsetsOrder() 44 - var res string 45 - var err error 46 44 47 45 // Note: golang compiler seems so behave differently depending on the current 48 46 // locale, so some conversions might behave differently. For that reason, we don't 49 47 // depend on particular conversions but in expected behaviors. 50 48 51 - res, err = ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{}) 49 + res, err := ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{}) 52 50 require.NoError(t, err) 53 51 assert.Equal(t, "ABC", res) 54 52
+23 -13
modules/git/repo_index.go
··· 50 50 } 51 51 52 52 // ReadTreeToTemporaryIndex reads a treeish to a temporary index file 53 - func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename, tmpDir string, cancel context.CancelFunc, err error) { 53 + func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (tmpIndexFilename, tmpDir string, cancel context.CancelFunc, err error) { 54 + defer func() { 55 + // if error happens and there is a cancel function, do clean up 56 + if err != nil && cancel != nil { 57 + cancel() 58 + cancel = nil 59 + } 60 + }() 61 + 62 + removeDirFn := func(dir string) func() { // it can't use the return value "tmpDir" directly because it is empty when error occurs 63 + return func() { 64 + if err := util.RemoveAll(dir); err != nil { 65 + log.Error("failed to remove tmp index dir: %v", err) 66 + } 67 + } 68 + } 69 + 54 70 tmpDir, err = os.MkdirTemp("", "index") 55 71 if err != nil { 56 - return filename, tmpDir, cancel, err 72 + return "", "", nil, err 57 73 } 58 74 59 - filename = filepath.Join(tmpDir, ".tmp-index") 60 - cancel = func() { 61 - err := util.RemoveAll(tmpDir) 62 - if err != nil { 63 - log.Error("failed to remove tmp index file: %v", err) 64 - } 65 - } 66 - err = repo.ReadTreeToIndex(treeish, filename) 75 + tmpIndexFilename = filepath.Join(tmpDir, ".tmp-index") 76 + cancel = removeDirFn(tmpDir) 77 + err = repo.ReadTreeToIndex(treeish, tmpIndexFilename) 67 78 if err != nil { 68 - defer cancel() 69 - return "", "", func() {}, err 79 + return "", "", cancel, err 70 80 } 71 - return filename, tmpDir, cancel, err 81 + return tmpIndexFilename, tmpDir, cancel, err 72 82 } 73 83 74 84 // EmptyIndex empties the index
+2 -3
modules/lfs/http_client.go
··· 16 16 "code.gitea.io/gitea/modules/json" 17 17 "code.gitea.io/gitea/modules/log" 18 18 "code.gitea.io/gitea/modules/proxy" 19 + "code.gitea.io/gitea/modules/setting" 19 20 ) 20 - 21 - const httpBatchSize = 20 22 21 23 22 // HTTPClient is used to communicate with the LFS server 24 23 // https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md ··· 30 29 31 30 // BatchSize returns the preferred size of batchs to process 32 31 func (c *HTTPClient) BatchSize() int { 33 - return httpBatchSize 32 + return setting.LFSClient.BatchSize 34 33 } 35 34 36 35 func newHTTPClient(endpoint *url.URL, httpTransport *http.Transport) *HTTPClient {
+1 -2
modules/markup/markdown/goldmark.go
··· 203 203 return ast.WalkContinue, nil 204 204 } 205 205 206 - var err error 207 - _, err = w.WriteString(fmt.Sprintf(`<i class="icon %s"></i>`, name)) 206 + _, err := w.WriteString(fmt.Sprintf(`<i class="icon %s"></i>`, name)) 208 207 if err != nil { 209 208 return ast.WalkStop, err 210 209 }
+2 -2
modules/packages/content_store.go
··· 37 37 return setting.Packages.Storage.MinioConfig.ServeDirect 38 38 } 39 39 40 - func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string) (*url.URL, error) { 41 - return s.store.URL(KeyToRelativePath(key), filename) 40 + func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string, reqParams url.Values) (*url.URL, error) { 41 + return s.store.URL(KeyToRelativePath(key), filename, reqParams) 42 42 } 43 43 44 44 // FIXME: Workaround to be removed in v1.20
+3 -2
modules/repository/repo.go
··· 341 341 342 342 for _, tag := range updates { 343 343 if _, err := db.GetEngine(ctx).Where("repo_id = ? AND lower_tag_name = ?", repo.ID, strings.ToLower(tag.Name)). 344 - Cols("sha1"). 344 + Cols("sha1", "created_unix"). 345 345 Update(&repo_model.Release{ 346 - Sha1: tag.Object.String(), 346 + Sha1: tag.Object.String(), 347 + CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()), 347 348 }); err != nil { 348 349 return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err) 349 350 }
+17 -4
modules/setting/lfs.go
··· 10 10 "code.gitea.io/gitea/modules/generate" 11 11 ) 12 12 13 - // LFS represents the configuration for Git LFS 13 + // LFS represents the server-side configuration for Git LFS. 14 + // Ideally these options should be in a section like "[lfs_server]", 15 + // but they are in "[server]" section due to historical reasons. 16 + // Could be refactored in the future while keeping backwards compatibility. 14 17 var LFS = struct { 15 18 StartServer bool `ini:"LFS_START_SERVER"` 16 19 JWTSecretBytes []byte `ini:"-"` 17 20 HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` 18 21 MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"` 19 22 LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"` 23 + MaxBatchSize int `ini:"LFS_MAX_BATCH_SIZE"` 20 24 21 25 Storage *Storage 26 + }{} 27 + 28 + // LFSClient represents configuration for Gitea's LFS clients, for example: mirroring upstream Git LFS 29 + var LFSClient = struct { 30 + BatchSize int `ini:"BATCH_SIZE"` 22 31 }{} 23 32 24 33 func loadLFSFrom(rootCfg ConfigProvider) error { 34 + mustMapSetting(rootCfg, "lfs_client", &LFSClient) 35 + 36 + mustMapSetting(rootCfg, "server", &LFS) 25 37 sec := rootCfg.Section("server") 26 - if err := sec.MapTo(&LFS); err != nil { 27 - return fmt.Errorf("failed to map LFS settings: %v", err) 28 - } 29 38 30 39 lfsSec, _ := rootCfg.GetSection("lfs") 31 40 ··· 50 59 // Rest of LFS service settings 51 60 if LFS.LocksPagingNum == 0 { 52 61 LFS.LocksPagingNum = 50 62 + } 63 + 64 + if LFSClient.BatchSize < 1 { 65 + LFSClient.BatchSize = 20 53 66 } 54 67 55 68 LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour)
+16
modules/setting/lfs_test.go
··· 100 100 assert.EqualValues(t, "gitea", LFS.Storage.MinioConfig.Bucket) 101 101 assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) 102 102 } 103 + 104 + func Test_LFSClientServerConfigs(t *testing.T) { 105 + iniStr := ` 106 + [server] 107 + LFS_MAX_BATCH_SIZE = 100 108 + [lfs_client] 109 + # will default to 20 110 + BATCH_SIZE = 0 111 + ` 112 + cfg, err := NewConfigProviderFromData(iniStr) 113 + assert.NoError(t, err) 114 + 115 + assert.NoError(t, loadLFSFrom(cfg)) 116 + assert.EqualValues(t, 100, LFS.MaxBatchSize) 117 + assert.EqualValues(t, 20, LFSClient.BatchSize) 118 + }
+1 -1
modules/storage/helper.go
··· 30 30 return fmt.Errorf("%s", s) 31 31 } 32 32 33 - func (s DiscardStorage) URL(_, _ string) (*url.URL, error) { 33 + func (s DiscardStorage) URL(_, _ string, _ url.Values) (*url.URL, error) { 34 34 return nil, fmt.Errorf("%s", s) 35 35 } 36 36
+1 -1
modules/storage/helper_test.go
··· 38 38 require.Error(t, err, string(tt)) 39 39 } 40 40 { 41 - got, err := tt.URL("path", "name") 41 + got, err := tt.URL("path", "name", nil) 42 42 assert.Nil(t, got) 43 43 require.Errorf(t, err, string(tt)) 44 44 }
+1 -1
modules/storage/local.go
··· 114 114 } 115 115 116 116 // URL gets the redirect URL to a file 117 - func (l *LocalStorage) URL(path, name string) (*url.URL, error) { 117 + func (l *LocalStorage) URL(path, name string, reqParams url.Values) (*url.URL, error) { 118 118 return nil, ErrURLNotSupported 119 119 } 120 120
+6 -2
modules/storage/minio.go
··· 276 276 } 277 277 278 278 // URL gets the redirect URL to a file. The presigned link is valid for 5 minutes. 279 - func (m *MinioStorage) URL(path, name string) (*url.URL, error) { 280 - reqParams := make(url.Values) 279 + func (m *MinioStorage) URL(path, name string, serveDirectReqParams url.Values) (*url.URL, error) { 280 + // copy serveDirectReqParams 281 + reqParams, err := url.ParseQuery(serveDirectReqParams.Encode()) 282 + if err != nil { 283 + return nil, err 284 + } 281 285 // TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we? 282 286 reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"") 283 287 u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), 5*time.Minute, reqParams)
+2 -2
modules/storage/storage.go
··· 63 63 Save(path string, r io.Reader, size int64) (int64, error) 64 64 Stat(path string) (os.FileInfo, error) 65 65 Delete(path string) error 66 - URL(path, name string) (*url.URL, error) 66 + URL(path, name string, reqParams url.Values) (*url.URL, error) 67 67 IterateObjects(path string, iterator func(path string, obj Object) error) error 68 68 } 69 69 ··· 131 131 ActionsArtifacts ObjectStorage = UninitializedStorage 132 132 ) 133 133 134 - // Init init the stoarge 134 + // Init init the storage 135 135 func Init() error { 136 136 for _, f := range []func() error{ 137 137 initAttachments,
+6
release-notes/5789.md
··· 1 + fix: [commit](https://codeberg.org/forgejo/forgejo/commit/362ad0ba39bdbc87202e349678e21fc2a75ff7cb) Update force-pushed tags too when syncing mirrors 2 + chore: [commit](https://codeberg.org/forgejo/forgejo/commit/b308bcca7c950b7f0d127ee4282019c2a9923299) Improved diff view performance 3 + fix: [commit](https://codeberg.org/forgejo/forgejo/commit/4c5bdddf7751a35985c08ba6506f1f30103749d6) Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled 4 + fix: [commit](https://codeberg.org/forgejo/forgejo/commit/2c5fdb108ff9e23e8f907fb6afe59177c6bb202e) Fix the missing menu in organization project view page 5 + feat: [commit](https://codeberg.org/forgejo/forgejo/commit/1e595979625e54d375a0eaa440b84ef5e17af160) Add new [lfs_client].BATCH_SIZE and [server].LFS_MAX_BATCH_SIZE config settings. 6 + fix: [commit](https://codeberg.org/forgejo/forgejo/commit/2358c0d899faec8311e46dcb0550041496bcd532) Properly clean temporary index files
+1 -1
routers/api/actions/artifacts.go
··· 437 437 for _, artifact := range artifacts { 438 438 var downloadURL string 439 439 if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect { 440 - u, err := ar.fs.URL(artifact.StoragePath, artifact.ArtifactName) 440 + u, err := ar.fs.URL(artifact.StoragePath, artifact.ArtifactName, nil) 441 441 if err != nil && !errors.Is(err, storage.ErrURLNotSupported) { 442 442 log.Error("Error getting serve direct url: %v", err) 443 443 }
+1 -1
routers/api/actions/artifactsv4.go
··· 530 530 respData := GetSignedArtifactURLResponse{} 531 531 532 532 if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect { 533 - u, err := storage.ActionsArtifacts.URL(artifact.StoragePath, artifact.ArtifactPath) 533 + u, err := storage.ActionsArtifacts.URL(artifact.StoragePath, artifact.ArtifactPath, nil) 534 534 if u != nil && err == nil { 535 535 respData.SignedUrl = u.String() 536 536 }
+3 -1
routers/api/packages/container/container.go
··· 689 689 } 690 690 691 691 func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) { 692 - s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob) 692 + serveDirectReqParams := make(url.Values) 693 + serveDirectReqParams.Set("response-content-type", pfd.Properties.GetByName(container_module.PropertyMediaType)) 694 + s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob, serveDirectReqParams) 693 695 if err != nil { 694 696 apiError(ctx, http.StatusInternalServerError, err) 695 697 return
+1 -1
routers/api/packages/maven/maven.go
··· 217 217 return 218 218 } 219 219 220 - s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb) 220 + s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb, nil) 221 221 if err != nil { 222 222 apiError(ctx, http.StatusInternalServerError, err) 223 223 return
+2 -2
routers/api/v1/repo/file.go
··· 214 214 215 215 if setting.LFS.Storage.MinioConfig.ServeDirect { 216 216 // If we have a signed url (S3, object storage), redirect to this directly. 217 - u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name()) 217 + u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name(), nil) 218 218 if u != nil && err == nil { 219 219 ctx.Redirect(u.String()) 220 220 return ··· 341 341 rPath := archiver.RelativePath() 342 342 if setting.RepoArchive.Storage.MinioConfig.ServeDirect { 343 343 // If we have a signed url (S3, object storage), redirect to this directly. 344 - u, err := storage.RepoArchives.URL(rPath, downloadName) 344 + u, err := storage.RepoArchives.URL(rPath, downloadName, nil) 345 345 if u != nil && err == nil { 346 346 ctx.Redirect(u.String()) 347 347 return
-1
routers/api/v1/repo/repo.go
··· 202 202 } 203 203 } 204 204 205 - var err error 206 205 repos, count, err := repo_model.SearchRepository(ctx, opts) 207 206 if err != nil { 208 207 ctx.JSON(http.StatusInternalServerError, api.SearchError{
-1
routers/api/v1/user/repo.go
··· 130 130 return 131 131 } 132 132 133 - var err error 134 133 repos, count, err := repo_model.SearchRepository(ctx, opts) 135 134 if err != nil { 136 135 ctx.Error(http.StatusInternalServerError, "SearchRepository", err)
+1 -1
routers/web/base.go
··· 39 39 rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/") 40 40 rPath = util.PathJoinRelX(rPath) 41 41 42 - u, err := objStore.URL(rPath, path.Base(rPath)) 42 + u, err := objStore.URL(rPath, path.Base(rPath), nil) 43 43 if err != nil { 44 44 if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { 45 45 log.Warn("Unable to find %s %s", prefix, rPath)
+2 -2
routers/web/repo/actions/view.go
··· 307 307 if validCursor { 308 308 length := step.LogLength - cursor.Cursor 309 309 offset := task.LogIndexes[index] 310 - var err error 311 310 logRows, err := actions.ReadLogs(ctx, task.LogInStorage, task.LogFilename, offset, length) 312 311 if err != nil { 313 312 ctx.Error(http.StatusInternalServerError, err.Error()) ··· 689 688 if len(artifacts) == 1 && artifacts[0].ArtifactName+".zip" == artifacts[0].ArtifactPath && artifacts[0].ContentEncoding == "application/zip" { 690 689 art := artifacts[0] 691 690 if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect { 692 - u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath) 691 + u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath, nil) 692 + 693 693 if u != nil && err == nil { 694 694 ctx.Redirect(u.String()) 695 695 return
-1
routers/web/repo/activity.go
··· 94 94 timeFrom = timeUntil.Add(-time.Hour * 168) 95 95 } 96 96 97 - var err error 98 97 authors, err := activities_model.GetActivityStatsTopAuthors(ctx, ctx.Repo.Repository, timeFrom, 10) 99 98 if err != nil { 100 99 ctx.ServerError("GetActivityStatsTopAuthors", err)
+1 -1
routers/web/repo/attachment.go
··· 134 134 135 135 if setting.Attachment.Storage.MinioConfig.ServeDirect { 136 136 // If we have a signed url (S3, object storage), redirect to this directly. 137 - u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name) 137 + u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name, nil) 138 138 139 139 if u != nil && err == nil { 140 140 ctx.Redirect(u.String())
+1
routers/web/repo/commit.go
··· 338 338 MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, 339 339 MaxFiles: maxFiles, 340 340 WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), 341 + FileOnly: fileOnly, 341 342 }, files...) 342 343 if err != nil { 343 344 ctx.NotFound("GetDiff", err)
+3
routers/web/repo/compare.go
··· 611 611 maxLines, maxFiles = -1, -1 612 612 } 613 613 614 + fileOnly := ctx.FormBool("file-only") 615 + 614 616 diff, err := gitdiff.GetDiff(ctx, ci.HeadGitRepo, 615 617 &gitdiff.DiffOptions{ 616 618 BeforeCommitID: beforeCommitID, ··· 621 623 MaxFiles: maxFiles, 622 624 WhitespaceBehavior: whitespaceBehavior, 623 625 DirectComparison: ci.DirectComparison, 626 + FileOnly: fileOnly, 624 627 }, ctx.FormStrings("files")...) 625 628 if err != nil { 626 629 ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
+2 -2
routers/web/repo/download.go
··· 54 54 } 55 55 56 56 if setting.LFS.Storage.MinioConfig.ServeDirect { 57 - // If we have a signed url (S3, object storage), redirect to this directly. 58 - u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name()) 57 + // If we have a signed url (S3, object storage, blob storage), redirect to this directly. 58 + u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name(), nil) 59 59 if u != nil && err == nil { 60 60 ctx.Redirect(u.String()) 61 61 return nil
+4 -3
routers/web/repo/pull.go
··· 614 614 var headBranchSha string 615 615 // HeadRepo may be missing 616 616 if pull.HeadRepo != nil { 617 - headGitRepo, err := gitrepo.OpenRepository(ctx, pull.HeadRepo) 617 + headGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pull.HeadRepo) 618 618 if err != nil { 619 - ctx.ServerError("OpenRepository", err) 619 + ctx.ServerError("RepositoryFromContextOrOpen", err) 620 620 return nil 621 621 } 622 - defer headGitRepo.Close() 622 + defer closer.Close() 623 623 624 624 if pull.Flow == issues_model.PullRequestFlowGithub { 625 625 headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch) ··· 966 966 MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, 967 967 MaxFiles: maxFiles, 968 968 WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)), 969 + FileOnly: fileOnly, 969 970 } 970 971 971 972 if !willShowSpecifiedCommit {
+1 -1
routers/web/repo/repo.go
··· 505 505 rPath := archiver.RelativePath() 506 506 if setting.RepoArchive.Storage.MinioConfig.ServeDirect { 507 507 // If we have a signed url (S3, object storage), redirect to this directly. 508 - u, err := storage.RepoArchives.URL(rPath, downloadName) 508 + u, err := storage.RepoArchives.URL(rPath, downloadName, nil) 509 509 if u != nil && err == nil { 510 510 if archiver.ReleaseID != 0 { 511 511 err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.ReleaseID, archiver.Type)
-1
routers/web/repo/view.go
··· 147 147 // this should be impossible; if subTreeEntry exists so should this. 148 148 continue 149 149 } 150 - var err error 151 150 childEntries, err := subTree.ListEntries() 152 151 if err != nil { 153 152 return "", nil, err
+26 -34
services/gitdiff/gitdiff.go
··· 379 379 } 380 380 381 381 // GetTailSection creates a fake DiffLineSection if the last section is not the end of the file 382 - func (diffFile *DiffFile) GetTailSection(gitRepo *git.Repository, leftCommitID, rightCommitID string) *DiffSection { 382 + func (diffFile *DiffFile) GetTailSection(gitRepo *git.Repository, leftCommit, rightCommit *git.Commit) *DiffSection { 383 383 if len(diffFile.Sections) == 0 || diffFile.Type != DiffFileChange || diffFile.IsBin || diffFile.IsLFSFile { 384 384 return nil 385 385 } 386 - leftCommit, err := gitRepo.GetCommit(leftCommitID) 387 - if err != nil { 388 - return nil 389 - } 390 - rightCommit, err := gitRepo.GetCommit(rightCommitID) 391 - if err != nil { 392 - return nil 393 - } 386 + 394 387 lastSection := diffFile.Sections[len(diffFile.Sections)-1] 395 388 lastLine := lastSection.Lines[len(lastSection.Lines)-1] 396 389 leftLineCount := getCommitFileLineCount(leftCommit, diffFile.Name) ··· 532 525 lastFile := createDiffFile(diff, line) 533 526 diff.End = lastFile.Name 534 527 diff.IsIncomplete = true 535 - _, err := io.Copy(io.Discard, reader) 536 - if err != nil { 537 - // By the definition of io.Copy this never returns io.EOF 538 - return diff, fmt.Errorf("error during io.Copy: %w", err) 539 - } 540 528 break parsingLoop 541 529 } 542 530 ··· 1097 1085 MaxFiles int 1098 1086 WhitespaceBehavior git.TrustedCmdArgs 1099 1087 DirectComparison bool 1088 + FileOnly bool 1100 1089 } 1101 1090 1102 1091 // GetDiff builds a Diff between two commits of a repository. ··· 1105 1094 func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff, error) { 1106 1095 repoPath := gitRepo.Path 1107 1096 1097 + var beforeCommit *git.Commit 1108 1098 commit, err := gitRepo.GetCommit(opts.AfterCommitID) 1109 1099 if err != nil { 1110 1100 return nil, err 1111 1101 } 1112 1102 1113 - cmdDiff := git.NewCommand(gitRepo.Ctx) 1103 + cmdCtx, cmdCancel := context.WithCancel(ctx) 1104 + defer cmdCancel() 1105 + 1106 + cmdDiff := git.NewCommand(cmdCtx) 1114 1107 objectFormat, err := gitRepo.GetObjectFormat() 1115 1108 if err != nil { 1116 1109 return nil, err ··· 1132 1125 AddArguments(opts.WhitespaceBehavior...). 1133 1126 AddDynamicArguments(actualBeforeCommitID, opts.AfterCommitID) 1134 1127 opts.BeforeCommitID = actualBeforeCommitID 1128 + 1129 + var err error 1130 + beforeCommit, err = gitRepo.GetCommit(opts.BeforeCommitID) 1131 + if err != nil { 1132 + return nil, err 1133 + } 1135 1134 } 1136 1135 1137 1136 // In git 2.31, git diff learned --skip-to which we can use to shortcut skip to file ··· 1166 1165 _ = writer.Close() 1167 1166 }() 1168 1167 1169 - diff, err := ParsePatch(ctx, opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, reader, parsePatchSkipToFile) 1168 + diff, err := ParsePatch(cmdCtx, opts.MaxLines, opts.MaxLineCharacters, opts.MaxFiles, reader, parsePatchSkipToFile) 1169 + // Ensure the git process is killed if it didn't exit already 1170 + cmdCancel() 1170 1171 if err != nil { 1171 1172 return nil, fmt.Errorf("unable to ParsePatch: %w", err) 1172 1173 } ··· 1207 1208 diffFile.IsGenerated = analyze.IsGenerated(diffFile.Name) 1208 1209 } 1209 1210 1210 - tailSection := diffFile.GetTailSection(gitRepo, opts.BeforeCommitID, opts.AfterCommitID) 1211 + tailSection := diffFile.GetTailSection(gitRepo, beforeCommit, commit) 1211 1212 if tailSection != nil { 1212 1213 diffFile.Sections = append(diffFile.Sections, tailSection) 1213 1214 } 1214 1215 } 1215 1216 1216 - separator := "..." 1217 - if opts.DirectComparison { 1218 - separator = ".." 1217 + if opts.FileOnly { 1218 + return diff, nil 1219 1219 } 1220 1220 1221 - diffPaths := []string{opts.BeforeCommitID + separator + opts.AfterCommitID} 1222 - if len(opts.BeforeCommitID) == 0 || opts.BeforeCommitID == objectFormat.EmptyObjectID().String() { 1223 - diffPaths = []string{objectFormat.EmptyTree().String(), opts.AfterCommitID} 1224 - } 1225 - diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) 1226 - if err != nil && strings.Contains(err.Error(), "no merge base") { 1227 - // git >= 2.28 now returns an error if base and head have become unrelated. 1228 - // previously it would return the results of git diff --shortstat base head so let's try that... 1229 - diffPaths = []string{opts.BeforeCommitID, opts.AfterCommitID} 1230 - diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) 1231 - } 1221 + stats, err := GetPullDiffStats(gitRepo, opts) 1232 1222 if err != nil { 1233 1223 return nil, err 1234 1224 } 1235 1225 1226 + diff.NumFiles, diff.TotalAddition, diff.TotalDeletion = stats.NumFiles, stats.TotalAddition, stats.TotalDeletion 1227 + 1236 1228 return diff, nil 1237 1229 } 1238 1230 1239 1231 type PullDiffStats struct { 1240 - TotalAddition, TotalDeletion int 1232 + NumFiles, TotalAddition, TotalDeletion int 1241 1233 } 1242 1234 1243 1235 // GetPullDiffStats ··· 1261 1253 diffPaths = []string{objectFormat.EmptyTree().String(), opts.AfterCommitID} 1262 1254 } 1263 1255 1264 - _, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) 1256 + diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) 1265 1257 if err != nil && strings.Contains(err.Error(), "no merge base") { 1266 1258 // git >= 2.28 now returns an error if base and head have become unrelated. 1267 1259 // previously it would return the results of git diff --shortstat base head so let's try that... 1268 1260 diffPaths = []string{opts.BeforeCommitID, opts.AfterCommitID} 1269 - _, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) 1261 + diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(gitRepo.Ctx, repoPath, nil, diffPaths...) 1270 1262 } 1271 1263 if err != nil { 1272 1264 return nil, err
-2
services/issue/template.go
··· 56 56 return GetDefaultTemplateConfig(), nil 57 57 } 58 58 59 - var err error 60 - 61 59 treeEntry, err := commit.GetTreeEntryByPath(path) 62 60 if err != nil { 63 61 return GetDefaultTemplateConfig(), err
+6 -1
services/lfs/server.go
··· 192 192 } 193 193 } 194 194 195 + if setting.LFS.MaxBatchSize != 0 && len(br.Objects) > setting.LFS.MaxBatchSize { 196 + writeStatus(ctx, http.StatusRequestEntityTooLarge) 197 + return 198 + } 199 + 195 200 contentStore := lfs_module.NewContentStore() 196 201 197 202 var responseObjects []*lfs_module.ObjectResponse ··· 480 485 var link *lfs_module.Link 481 486 if setting.LFS.Storage.MinioConfig.ServeDirect { 482 487 // If we have a signed url (S3, object storage), redirect to this directly. 483 - u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid) 488 + u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid, nil) 484 489 if u != nil && err == nil { 485 490 // Presigned url does not need the Authorization header 486 491 // https://github.com/go-gitea/gitea/issues/21525
+3 -3
services/packages/packages.go
··· 602 602 return nil, nil, nil, err 603 603 } 604 604 605 - return GetPackageBlobStream(ctx, pf, pb) 605 + return GetPackageBlobStream(ctx, pf, pb, nil) 606 606 } 607 607 608 608 // GetPackageBlobStream returns the content of the specific package blob 609 609 // If the storage supports direct serving and it's enabled, only the direct serving url is returned. 610 - func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { 610 + func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) { 611 611 key := packages_module.BlobHash256Key(pb.HashSHA256) 612 612 613 613 cs := packages_module.NewContentStore() ··· 617 617 var err error 618 618 619 619 if cs.ShouldServeDirect() { 620 - u, err = cs.GetServeDirectURL(key, pf.Name) 620 + u, err = cs.GetServeDirectURL(key, pf.Name, serveDirectReqParams) 621 621 if err != nil && !errors.Is(err, storage.ErrURLNotSupported) { 622 622 log.Error("Error getting serve direct url: %v", err) 623 623 }
+9 -5
templates/org/projects/view.tmpl
··· 1 1 {{template "base/head" .}} 2 - <div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project"> 3 - {{template "shared/user/org_profile_avatar" .}} 4 - <div class="ui container tw-mb-4"> 5 - {{template "user/overview/header" .}} 6 - </div> 2 + <div role="main" aria-label="{{.Title}}" class="page-content organization repository projects view-project"> 3 + {{if .ContextUser.IsOrganization}} 4 + {{template "org/header" .}} 5 + {{else}} 6 + {{template "shared/user/org_profile_avatar" .}} 7 + <div class="ui container tw-mb-4"> 8 + {{template "user/overview/header" .}} 9 + </div> 10 + {{end}} 7 11 <div class="ui container fluid padded"> 8 12 {{template "projects/view" .}} 9 13 </div>
+1 -1
templates/repo/issue/view_title.tmpl
··· 117 117 {{$sameBase := ne $.BaseName $.HeadUserName}} 118 118 {{$differentBranch := ne . $.HeadBranch}} 119 119 {{if or $sameBase $differentBranch}} 120 - <div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-branch="{{.}}">{{$.BaseName}}{{if $.HeadRepo}}/{{$.HeadRepo}}{{end}}:{{.}}</div> 120 + <div class="item {{if eq $.BaseBranch .}}selected{{end}}" data-branch="{{.}}">{{$.BaseName}}:{{.}}</div> 121 121 {{end}} 122 122 {{end}} 123 123 </div>
+42 -32
tests/integration/api_actions_artifact_test.go
··· 38 38 39 39 // get upload url 40 40 idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") 41 - url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact/abc.txt" 41 + url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact/abc-2.txt" 42 42 43 43 // upload artifact chunk 44 - body := strings.Repeat("A", 1024) 44 + body := strings.Repeat("C", 1024) 45 45 req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)). 46 46 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). 47 47 SetHeader("Content-Range", "bytes 0-1023/1024"). 48 48 SetHeader("x-tfs-filelength", "1024"). 49 - SetHeader("x-actions-results-md5", "1HsSe8LeLWh93ILaw1TEFQ==") // base64(md5(body)) 49 + SetHeader("x-actions-results-md5", "XVlf820rMInUi64wmMi6EA==") // base64(md5(body)) 50 50 MakeRequest(t, req, http.StatusOK) 51 51 52 52 t.Logf("Create artifact confirm") 53 53 54 54 // confirm artifact upload 55 - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact"). 55 + req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-single"). 56 56 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 57 57 MakeRequest(t, req, http.StatusOK) 58 58 } ··· 115 115 resp := MakeRequest(t, req, http.StatusOK) 116 116 var listResp listArtifactsResponse 117 117 DecodeJSON(t, resp, &listResp) 118 - assert.Equal(t, int64(1), listResp.Count) 119 - assert.Equal(t, "artifact", listResp.Value[0].Name) 120 - assert.Contains(t, listResp.Value[0].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") 118 + assert.Equal(t, int64(2), listResp.Count) 119 + 120 + // Return list might be in any order. Get one file. 121 + var artifactIdx int 122 + for i, artifact := range listResp.Value { 123 + if artifact.Name == "artifact-download" { 124 + artifactIdx = i 125 + break 126 + } 127 + } 128 + assert.NotNil(t, artifactIdx) 129 + assert.Equal(t, "artifact-download", listResp.Value[artifactIdx].Name) 130 + assert.Contains(t, listResp.Value[artifactIdx].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") 121 131 122 - idx := strings.Index(listResp.Value[0].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") 123 - url := listResp.Value[0].FileContainerResourceURL[idx+1:] + "?itemPath=artifact" 132 + idx := strings.Index(listResp.Value[artifactIdx].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") 133 + url := listResp.Value[artifactIdx].FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download" 124 134 req = NewRequest(t, "GET", url). 125 135 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 126 136 resp = MakeRequest(t, req, http.StatusOK) 127 137 var downloadResp downloadArtifactResponse 128 138 DecodeJSON(t, resp, &downloadResp) 129 139 assert.Len(t, downloadResp.Value, 1) 130 - assert.Equal(t, "artifact/abc.txt", downloadResp.Value[0].Path) 131 - assert.Equal(t, "file", downloadResp.Value[0].ItemType) 132 - assert.Contains(t, downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") 140 + assert.Equal(t, "artifact-download/abc.txt", downloadResp.Value[artifactIdx].Path) 141 + assert.Equal(t, "file", downloadResp.Value[artifactIdx].ItemType) 142 + assert.Contains(t, downloadResp.Value[artifactIdx].ContentLocation, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") 133 143 134 - idx = strings.Index(downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/") 135 - url = downloadResp.Value[0].ContentLocation[idx:] 144 + idx = strings.Index(downloadResp.Value[artifactIdx].ContentLocation, "/api/actions_pipeline/_apis/pipelines/") 145 + url = downloadResp.Value[artifactIdx].ContentLocation[idx:] 136 146 req = NewRequest(t, "GET", url). 137 147 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 138 148 resp = MakeRequest(t, req, http.StatusOK) 149 + 139 150 body := strings.Repeat("A", 1024) 140 - assert.Equal(t, resp.Body.String(), body) 151 + assert.Equal(t, body, resp.Body.String()) 141 152 } 142 153 143 154 func TestActionsArtifactUploadMultipleFile(t *testing.T) { ··· 163 174 164 175 files := []uploadingFile{ 165 176 { 166 - Path: "abc.txt", 167 - Content: strings.Repeat("A", 1024), 168 - MD5: "1HsSe8LeLWh93ILaw1TEFQ==", 177 + Path: "abc-3.txt", 178 + Content: strings.Repeat("D", 1024), 179 + MD5: "9nqj7E8HZmfQtPifCJ5Zww==", 169 180 }, 170 181 { 171 - Path: "xyz/def.txt", 172 - Content: strings.Repeat("B", 1024), 173 - MD5: "6fgADK/7zjadf+6cB9Q1CQ==", 182 + Path: "xyz/def-2.txt", 183 + Content: strings.Repeat("E", 1024), 184 + MD5: "/s1kKvxeHlUX85vaTaVxuA==", 174 185 }, 175 186 } 176 187 ··· 199 210 func TestActionsArtifactDownloadMultiFiles(t *testing.T) { 200 211 defer tests.PrepareTestEnv(t)() 201 212 202 - const testArtifactName = "multi-files" 213 + const testArtifactName = "multi-file-download" 203 214 204 215 req := NewRequest(t, "GET", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts"). 205 216 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") ··· 226 237 DecodeJSON(t, resp, &downloadResp) 227 238 assert.Len(t, downloadResp.Value, 2) 228 239 229 - downloads := [][]string{{"multi-files/abc.txt", "A"}, {"multi-files/xyz/def.txt", "B"}} 240 + downloads := [][]string{{"multi-file-download/abc.txt", "B"}, {"multi-file-download/xyz/def.txt", "C"}} 230 241 for _, v := range downloadResp.Value { 231 242 var bodyChar string 232 243 var path string ··· 247 258 req = NewRequest(t, "GET", url). 248 259 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 249 260 resp = MakeRequest(t, req, http.StatusOK) 250 - body := strings.Repeat(bodyChar, 1024) 251 - assert.Equal(t, resp.Body.String(), body) 261 + assert.Equal(t, strings.Repeat(bodyChar, 1024), resp.Body.String()) 252 262 } 253 263 } 254 264 ··· 300 310 DecodeJSON(t, resp, &listResp) 301 311 302 312 idx := strings.Index(listResp.Value[0].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") 303 - url := listResp.Value[0].FileContainerResourceURL[idx+1:] + "?itemPath=artifact" 313 + url := listResp.Value[0].FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download" 304 314 req = NewRequest(t, "GET", url). 305 315 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 306 316 resp = MakeRequest(t, req, http.StatusOK) ··· 320 330 // upload same artifact, it uses 4096 B 321 331 req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ 322 332 Type: "actions_storage", 323 - Name: "artifact", 333 + Name: "artifact-download", 324 334 }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 325 335 resp := MakeRequest(t, req, http.StatusOK) 326 336 var uploadResp uploadArtifactResponse 327 337 DecodeJSON(t, resp, &uploadResp) 328 338 329 339 idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") 330 - url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact/abc.txt" 340 + url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact-download/abc.txt" 331 341 body := strings.Repeat("B", 4096) 332 342 req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)). 333 343 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). ··· 337 347 MakeRequest(t, req, http.StatusOK) 338 348 339 349 // confirm artifact upload 340 - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact"). 350 + req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-download"). 341 351 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 342 352 MakeRequest(t, req, http.StatusOK) 343 353 } ··· 352 362 353 363 var uploadedItem listArtifactsResponseItem 354 364 for _, item := range listResp.Value { 355 - if item.Name == "artifact" { 365 + if item.Name == "artifact-download" { 356 366 uploadedItem = item 357 367 break 358 368 } 359 369 } 360 - assert.Equal(t, "artifact", uploadedItem.Name) 370 + assert.Equal(t, "artifact-download", uploadedItem.Name) 361 371 362 372 idx := strings.Index(uploadedItem.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") 363 - url := uploadedItem.FileContainerResourceURL[idx+1:] + "?itemPath=artifact" 373 + url := uploadedItem.FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download" 364 374 req = NewRequest(t, "GET", url). 365 375 AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") 366 376 resp = MakeRequest(t, req, http.StatusOK)
+12 -12
tests/integration/api_actions_artifact_v4_test.go
··· 313 313 314 314 // acquire artifact upload url 315 315 req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts", toProtoJSON(&actions.ListArtifactsRequest{ 316 - NameFilter: wrapperspb.String("artifact"), 316 + NameFilter: wrapperspb.String("artifact-v4-download"), 317 317 WorkflowRunBackendId: "792", 318 318 WorkflowJobRunBackendId: "193", 319 319 })).AddTokenAuth(token) ··· 324 324 325 325 // confirm artifact upload 326 326 req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL", toProtoJSON(&actions.GetSignedArtifactURLRequest{ 327 - Name: "artifact", 327 + Name: "artifact-v4-download", 328 328 WorkflowRunBackendId: "792", 329 329 WorkflowJobRunBackendId: "193", 330 330 })). ··· 336 336 337 337 req = NewRequest(t, "GET", finalizeResp.SignedUrl) 338 338 resp = MakeRequest(t, req, http.StatusOK) 339 - body := strings.Repeat("A", 1024) 339 + body := strings.Repeat("D", 1024) 340 340 assert.Equal(t, "bytes", resp.Header().Get("accept-ranges")) 341 341 assert.Equal(t, body, resp.Body.String()) 342 342 343 343 // Download artifact via user-facing URL 344 - req = NewRequest(t, "GET", "/user5/repo4/actions/runs/188/artifacts/artifact") 344 + req = NewRequest(t, "GET", "/user5/repo4/actions/runs/188/artifacts/artifact-v4-download") 345 345 resp = MakeRequest(t, req, http.StatusOK) 346 346 assert.Equal(t, "bytes", resp.Header().Get("accept-ranges")) 347 347 assert.Equal(t, body, resp.Body.String()) 348 348 349 349 // Partial artifact download 350 - req = NewRequest(t, "GET", "/user5/repo4/actions/runs/188/artifacts/artifact").SetHeader("range", "bytes=0-99") 350 + req = NewRequest(t, "GET", "/user5/repo4/actions/runs/188/artifacts/artifact-v4-download").SetHeader("range", "bytes=0-99") 351 351 resp = MakeRequest(t, req, http.StatusPartialContent) 352 - body = strings.Repeat("A", 100) 352 + body = strings.Repeat("D", 100) 353 353 assert.Equal(t, "bytes 0-99/1024", resp.Header().Get("content-range")) 354 354 assert.Equal(t, body, resp.Body.String()) 355 355 } ··· 357 357 func TestActionsArtifactV4DownloadRange(t *testing.T) { 358 358 defer tests.PrepareTestEnv(t)() 359 359 360 - bstr := strings.Repeat("B", 100) 360 + bstr := strings.Repeat("D", 100) 361 361 body := strings.Repeat("A", 100) + bstr 362 362 token := uploadArtifact(t, body) 363 363 364 364 // Download (Actions API) 365 365 req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL", toProtoJSON(&actions.GetSignedArtifactURLRequest{ 366 - Name: "artifact", 366 + Name: "artifact-v4-download", 367 367 WorkflowRunBackendId: "792", 368 368 WorkflowJobRunBackendId: "193", 369 369 })). ··· 375 375 376 376 req = NewRequest(t, "GET", finalizeResp.SignedUrl).SetHeader("range", "bytes=100-199") 377 377 resp = MakeRequest(t, req, http.StatusPartialContent) 378 - assert.Equal(t, "bytes 100-199/200", resp.Header().Get("content-range")) 378 + assert.Equal(t, "bytes 100-199/1024", resp.Header().Get("content-range")) 379 379 assert.Equal(t, bstr, resp.Body.String()) 380 380 381 381 // Download (user-facing API) 382 - req = NewRequest(t, "GET", "/user5/repo4/actions/runs/188/artifacts/artifact").SetHeader("range", "bytes=100-199") 382 + req = NewRequest(t, "GET", "/user5/repo4/actions/runs/188/artifacts/artifact-v4-download").SetHeader("range", "bytes=100-199") 383 383 resp = MakeRequest(t, req, http.StatusPartialContent) 384 - assert.Equal(t, "bytes 100-199/200", resp.Header().Get("content-range")) 384 + assert.Equal(t, "bytes 100-199/1024", resp.Header().Get("content-range")) 385 385 assert.Equal(t, bstr, resp.Body.String()) 386 386 } 387 387 ··· 393 393 394 394 // delete artifact by name 395 395 req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/DeleteArtifact", toProtoJSON(&actions.DeleteArtifactRequest{ 396 - Name: "artifact", 396 + Name: "artifact-v4-download", 397 397 WorkflowRunBackendId: "792", 398 398 WorkflowJobRunBackendId: "193", 399 399 })).AddTokenAuth(token)
+17
tests/test_utils.go
··· 224 224 t.Logf("PrepareTestEnv: all processes cancelled within %s", time.Since(start)) 225 225 } 226 226 227 + func PrepareArtifactsStorage(t testing.TB) { 228 + // prepare actions artifacts directory and files 229 + require.NoError(t, storage.Clean(storage.ActionsArtifacts)) 230 + 231 + s, err := storage.NewStorage(setting.LocalStorageType, &setting.Storage{ 232 + Path: filepath.Join(filepath.Dir(setting.AppPath), "tests", "testdata", "data", "artifacts"), 233 + }) 234 + require.NoError(t, err) 235 + require.NoError(t, s.IterateObjects("", func(p string, obj storage.Object) error { 236 + _, err = storage.Copy(storage.ActionsArtifacts, p, s, p) 237 + return err 238 + })) 239 + } 240 + 227 241 func PrepareTestEnv(t testing.TB, skip ...int) func() { 228 242 t.Helper() 229 243 ourSkip := 1 ··· 262 276 _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755) 263 277 } 264 278 } 279 + 280 + // Initialize actions artifact data 281 + PrepareArtifactsStorage(t) 265 282 266 283 // load LFS object fixtures 267 284 // (LFS storage can be on any of several backends, including remote servers, so we init it with the storage API)
+1
tests/testdata/data/artifacts/26/1/1712166500347189545.chunk
··· 1 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+1
tests/testdata/data/artifacts/26/19/1712348022422036662.chunk
··· 1 + BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
+1
tests/testdata/data/artifacts/26/20/1712348022423431524.chunk
··· 1 + CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+1
tests/testdata/data/artifacts/27/5/1730330775594233150.chunk
··· 1 + DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD