mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Stream repo zip/tar.gz/bundle achives by default (#35487)
Initial implementation of linked proposal. * Closes #29942 * Fix #34003 * Fix #30443 --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -54,6 +54,12 @@ var ( | |||||||
| 		AllowForkWithoutMaximumLimit            bool | 		AllowForkWithoutMaximumLimit            bool | ||||||
| 		AllowForkIntoSameOwner                  bool | 		AllowForkIntoSameOwner                  bool | ||||||
|  |  | ||||||
|  | 		// StreamArchives makes Gitea stream git archive files to the client directly instead of creating an archive first. | ||||||
|  | 		// Ideally all users should use this streaming method. However, at the moment we don't know whether there are | ||||||
|  | 		// any users who still need the old behavior, so we introduce this option, intentionally not documenting it. | ||||||
|  | 		// After one or two releases, if no one complains, we will remove this option and always use streaming. | ||||||
|  | 		StreamArchives bool | ||||||
|  |  | ||||||
| 		// Repository editor settings | 		// Repository editor settings | ||||||
| 		Editor struct { | 		Editor struct { | ||||||
| 			LineWrapExtensions []string | 			LineWrapExtensions []string | ||||||
| @@ -167,6 +173,7 @@ var ( | |||||||
| 		DisableStars:                            false, | 		DisableStars:                            false, | ||||||
| 		DefaultBranch:                           "main", | 		DefaultBranch:                           "main", | ||||||
| 		AllowForkWithoutMaximumLimit:            true, | 		AllowForkWithoutMaximumLimit:            true, | ||||||
|  | 		StreamArchives:                          true, | ||||||
|  |  | ||||||
| 		// Repository editor settings | 		// Repository editor settings | ||||||
| 		Editor: struct { | 		Editor: struct { | ||||||
|   | |||||||
| @@ -1247,7 +1247,7 @@ func Routes() *web.Router { | |||||||
| 				}, reqToken()) | 				}, reqToken()) | ||||||
| 				m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile) | 				m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile) | ||||||
| 				m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS) | 				m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS) | ||||||
| 				m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive) | 				m.Methods("HEAD,GET", "/archive/*", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true), repo.GetArchive) | ||||||
| 				m.Combo("/forks").Get(repo.ListForks). | 				m.Combo("/forks").Get(repo.ListForks). | ||||||
| 					Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork) | 					Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork) | ||||||
| 				m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream) | 				m.Post("/merge-upstream", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeCode), bind(api.MergeUpstreamRequest{}), repo.MergeUpstream) | ||||||
| @@ -1466,7 +1466,7 @@ func Routes() *web.Router { | |||||||
| 					m.Delete("", repo.DeleteAvatar) | 					m.Delete("", repo.DeleteAvatar) | ||||||
| 				}, reqAdmin(), reqToken()) | 				}, reqAdmin(), reqToken()) | ||||||
|  |  | ||||||
| 				m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive) | 				m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true), repo.DownloadArchive) | ||||||
| 			}, repoAssignment(), checkTokenPublicOnly()) | 			}, repoAssignment(), checkTokenPublicOnly()) | ||||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) | 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,14 +4,29 @@ | |||||||
| package repo | package repo | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/gitrepo" |  | ||||||
| 	"code.gitea.io/gitea/services/context" | 	"code.gitea.io/gitea/services/context" | ||||||
| 	archiver_service "code.gitea.io/gitea/services/repository/archiver" | 	archiver_service "code.gitea.io/gitea/services/repository/archiver" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | func serveRepoArchive(ctx *context.APIContext, reqFileName string) { | ||||||
|  | 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, reqFileName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { | ||||||
|  | 			ctx.APIError(http.StatusBadRequest, err) | ||||||
|  | 		} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) { | ||||||
|  | 			ctx.APIError(http.StatusNotFound, err) | ||||||
|  | 		} else { | ||||||
|  | 			ctx.APIErrorInternal(err) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	archiver_service.ServeRepoArchive(ctx.Base, ctx.Repo.Repository, ctx.Repo.GitRepo, aReq) | ||||||
|  | } | ||||||
|  |  | ||||||
| func DownloadArchive(ctx *context.APIContext) { | func DownloadArchive(ctx *context.APIContext) { | ||||||
| 	var tp git.ArchiveType | 	var tp git.ArchiveType | ||||||
| 	switch ballType := ctx.PathParam("ball_type"); ballType { | 	switch ballType := ctx.PathParam("ball_type"); ballType { | ||||||
| @@ -25,27 +40,5 @@ func DownloadArchive(ctx *context.APIContext) { | |||||||
| 		ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType) | 		ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	serveRepoArchive(ctx, ctx.PathParam("*")+"."+tp.String()) | ||||||
| 	if ctx.Repo.GitRepo == nil { |  | ||||||
| 		var err error |  | ||||||
| 		ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.APIErrorInternal(err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")+"."+tp.String()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.APIErrorInternal(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	archive, err := r.Await(ctx) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.APIErrorInternal(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	download(ctx, r.GetArchiveName(), archive) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,9 +15,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	git_model "code.gitea.io/gitea/models/git" | 	git_model "code.gitea.io/gitea/models/git" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" |  | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/gitrepo" |  | ||||||
| 	"code.gitea.io/gitea/modules/httpcache" | 	"code.gitea.io/gitea/modules/httpcache" | ||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| 	"code.gitea.io/gitea/modules/lfs" | 	"code.gitea.io/gitea/modules/lfs" | ||||||
| @@ -31,7 +29,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/routers/common" | 	"code.gitea.io/gitea/routers/common" | ||||||
| 	"code.gitea.io/gitea/services/context" | 	"code.gitea.io/gitea/services/context" | ||||||
| 	pull_service "code.gitea.io/gitea/services/pull" | 	pull_service "code.gitea.io/gitea/services/pull" | ||||||
| 	archiver_service "code.gitea.io/gitea/services/repository/archiver" |  | ||||||
| 	files_service "code.gitea.io/gitea/services/repository/files" | 	files_service "code.gitea.io/gitea/services/repository/files" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -282,74 +279,7 @@ func GetArchive(ctx *context.APIContext) { | |||||||
| 	//   "404": | 	//   "404": | ||||||
| 	//     "$ref": "#/responses/notFound" | 	//     "$ref": "#/responses/notFound" | ||||||
|  |  | ||||||
| 	if ctx.Repo.GitRepo == nil { | 	serveRepoArchive(ctx, ctx.PathParam("*")) | ||||||
| 		var err error |  | ||||||
| 		ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.APIErrorInternal(err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	archiveDownload(ctx) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func archiveDownload(ctx *context.APIContext) { |  | ||||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { |  | ||||||
| 			ctx.APIError(http.StatusBadRequest, err) |  | ||||||
| 		} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) { |  | ||||||
| 			ctx.APIError(http.StatusNotFound, err) |  | ||||||
| 		} else { |  | ||||||
| 			ctx.APIErrorInternal(err) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	archiver, err := aReq.Await(ctx) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.APIErrorInternal(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	download(ctx, aReq.GetArchiveName(), archiver) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func download(ctx *context.APIContext, archiveName string, archiver *repo_model.RepoArchiver) { |  | ||||||
| 	downloadName := ctx.Repo.Repository.Name + "-" + archiveName |  | ||||||
|  |  | ||||||
| 	// Add nix format link header so tarballs lock correctly: |  | ||||||
| 	// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md |  | ||||||
| 	ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.%s?rev=%s>; rel="immutable"`, |  | ||||||
| 		ctx.Repo.Repository.APIURL(), |  | ||||||
| 		archiver.CommitID, |  | ||||||
| 		archiver.Type.String(), |  | ||||||
| 		archiver.CommitID, |  | ||||||
| 	)) |  | ||||||
|  |  | ||||||
| 	rPath := archiver.RelativePath() |  | ||||||
| 	if setting.RepoArchive.Storage.ServeDirect() { |  | ||||||
| 		// If we have a signed url (S3, object storage), redirect to this directly. |  | ||||||
| 		u, err := storage.RepoArchives.URL(rPath, downloadName, ctx.Req.Method, nil) |  | ||||||
| 		if u != nil && err == nil { |  | ||||||
| 			ctx.Redirect(u.String()) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// If we have matched and access to release or issue |  | ||||||
| 	fr, err := storage.RepoArchives.Open(rPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.APIErrorInternal(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	defer fr.Close() |  | ||||||
|  |  | ||||||
| 	ctx.ServeContent(fr, &context.ServeHeaderOptions{ |  | ||||||
| 		Filename:     downloadName, |  | ||||||
| 		LastModified: archiver.CreatedUnix.AsLocalTime(), |  | ||||||
| 	}) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetEditorconfig get editor config of a repository | // GetEditorconfig get editor config of a repository | ||||||
|   | |||||||
| @@ -24,7 +24,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/optional" | 	"code.gitea.io/gitea/modules/optional" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" | 	repo_module "code.gitea.io/gitea/modules/repository" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/storage" |  | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/templates" | 	"code.gitea.io/gitea/modules/templates" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| @@ -376,53 +375,19 @@ func Download(ctx *context.Context) { | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	archiver_service.ServeRepoArchive(ctx.Base, ctx.Repo.Repository, ctx.Repo.GitRepo, aReq) | ||||||
| 	archiver, err := aReq.Await(ctx) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("archiver.Await", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	download(ctx, aReq.GetArchiveName(), archiver) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func download(ctx *context.Context, archiveName string, archiver *repo_model.RepoArchiver) { |  | ||||||
| 	downloadName := ctx.Repo.Repository.Name + "-" + archiveName |  | ||||||
|  |  | ||||||
| 	// Add nix format link header so tarballs lock correctly: |  | ||||||
| 	// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md |  | ||||||
| 	ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.tar.gz?rev=%s>; rel="immutable"`, |  | ||||||
| 		ctx.Repo.Repository.APIURL(), |  | ||||||
| 		archiver.CommitID, archiver.CommitID)) |  | ||||||
|  |  | ||||||
| 	rPath := archiver.RelativePath() |  | ||||||
| 	if setting.RepoArchive.Storage.ServeDirect() { |  | ||||||
| 		// If we have a signed url (S3, object storage), redirect to this directly. |  | ||||||
| 		u, err := storage.RepoArchives.URL(rPath, downloadName, ctx.Req.Method, nil) |  | ||||||
| 		if u != nil && err == nil { |  | ||||||
| 			ctx.Redirect(u.String()) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// If we have matched and access to release or issue |  | ||||||
| 	fr, err := storage.RepoArchives.Open(rPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("Open", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	defer fr.Close() |  | ||||||
|  |  | ||||||
| 	ctx.ServeContent(fr, &context.ServeHeaderOptions{ |  | ||||||
| 		Filename:     downloadName, |  | ||||||
| 		LastModified: archiver.CreatedUnix.AsLocalTime(), |  | ||||||
| 	}) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // InitiateDownload will enqueue an archival request, as needed.  It may submit | // InitiateDownload will enqueue an archival request, as needed.  It may submit | ||||||
| // a request that's already in-progress, but the archiver service will just | // a request that's already in-progress, but the archiver service will just | ||||||
| // kind of drop it on the floor if this is the case. | // kind of drop it on the floor if this is the case. | ||||||
| func InitiateDownload(ctx *context.Context) { | func InitiateDownload(ctx *context.Context) { | ||||||
|  | 	if setting.Repository.StreamArchives { | ||||||
|  | 		ctx.JSON(http.StatusOK, map[string]any{ | ||||||
|  | 			"complete": true, | ||||||
|  | 		}) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")) | 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.HTTPError(http.StatusBadRequest, "invalid archive request") | 		ctx.HTTPError(http.StatusBadRequest, "invalid archive request") | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| @@ -17,11 +18,13 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/gitrepo" | 	"code.gitea.io/gitea/modules/gitrepo" | ||||||
| 	"code.gitea.io/gitea/modules/graceful" | 	"code.gitea.io/gitea/modules/graceful" | ||||||
|  | 	"code.gitea.io/gitea/modules/httplib" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/process" | 	"code.gitea.io/gitea/modules/process" | ||||||
| 	"code.gitea.io/gitea/modules/queue" | 	"code.gitea.io/gitea/modules/queue" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/storage" | 	"code.gitea.io/gitea/modules/storage" | ||||||
|  | 	gitea_context "code.gitea.io/gitea/services/context" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ArchiveRequest defines the parameters of an archive request, which notably | // ArchiveRequest defines the parameters of an archive request, which notably | ||||||
| @@ -138,6 +141,25 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Stream satisfies the ArchiveRequest being passed in.  Processing | ||||||
|  | // will occur directly in this routine. | ||||||
|  | func (aReq *ArchiveRequest) Stream(ctx context.Context, gitRepo *git.Repository, w io.Writer) error { | ||||||
|  | 	if aReq.Type == git.ArchiveBundle { | ||||||
|  | 		return gitRepo.CreateBundle( | ||||||
|  | 			ctx, | ||||||
|  | 			aReq.CommitID, | ||||||
|  | 			w, | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 	return gitRepo.CreateArchive( | ||||||
|  | 		ctx, | ||||||
|  | 		aReq.Type, | ||||||
|  | 		w, | ||||||
|  | 		setting.Repository.PrefixArchiveFiles, | ||||||
|  | 		aReq.CommitID, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
| // doArchive satisfies the ArchiveRequest being passed in.  Processing | // doArchive satisfies the ArchiveRequest being passed in.  Processing | ||||||
| // will occur in a separate goroutine, as this phase may take a while to | // will occur in a separate goroutine, as this phase may take a while to | ||||||
| // complete.  If the archive already exists, doArchive will not do | // complete.  If the archive already exists, doArchive will not do | ||||||
| @@ -204,31 +226,17 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver | |||||||
| 	} | 	} | ||||||
| 	defer gitRepo.Close() | 	defer gitRepo.Close() | ||||||
|  |  | ||||||
| 	go func(done chan error, w *io.PipeWriter, archiver *repo_model.RepoArchiver, gitRepo *git.Repository) { | 	go func(done chan error, w *io.PipeWriter, archiveReq *ArchiveRequest, gitRepo *git.Repository) { | ||||||
| 		defer func() { | 		defer func() { | ||||||
| 			if r := recover(); r != nil { | 			if r := recover(); r != nil { | ||||||
| 				done <- fmt.Errorf("%v", r) | 				done <- fmt.Errorf("%v", r) | ||||||
| 			} | 			} | ||||||
| 		}() | 		}() | ||||||
|  |  | ||||||
| 		if archiver.Type == git.ArchiveBundle { | 		err := archiveReq.Stream(ctx, gitRepo, w) | ||||||
| 			err = gitRepo.CreateBundle( |  | ||||||
| 				ctx, |  | ||||||
| 				archiver.CommitID, |  | ||||||
| 				w, |  | ||||||
| 			) |  | ||||||
| 		} else { |  | ||||||
| 			err = gitRepo.CreateArchive( |  | ||||||
| 				ctx, |  | ||||||
| 				archiver.Type, |  | ||||||
| 				w, |  | ||||||
| 				setting.Repository.PrefixArchiveFiles, |  | ||||||
| 				archiver.CommitID, |  | ||||||
| 			) |  | ||||||
| 		} |  | ||||||
| 		_ = w.CloseWithError(err) | 		_ = w.CloseWithError(err) | ||||||
| 		done <- err | 		done <- err | ||||||
| 	}(done, w, archiver, gitRepo) | 	}(done, w, r, gitRepo) | ||||||
|  |  | ||||||
| 	// TODO: add lfs data to zip | 	// TODO: add lfs data to zip | ||||||
| 	// TODO: add submodule data to zip | 	// TODO: add submodule data to zip | ||||||
| @@ -338,3 +346,54 @@ func DeleteRepositoryArchives(ctx context.Context) error { | |||||||
| 	} | 	} | ||||||
| 	return storage.Clean(storage.RepoArchives) | 	return storage.Clean(storage.RepoArchives) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func ServeRepoArchive(ctx *gitea_context.Base, repo *repo_model.Repository, gitRepo *git.Repository, archiveReq *ArchiveRequest) { | ||||||
|  | 	// Add nix format link header so tarballs lock correctly: | ||||||
|  | 	// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md | ||||||
|  | 	ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.%s?rev=%s>; rel="immutable"`, | ||||||
|  | 		repo.APIURL(), | ||||||
|  | 		archiveReq.CommitID, | ||||||
|  | 		archiveReq.Type.String(), | ||||||
|  | 		archiveReq.CommitID, | ||||||
|  | 	)) | ||||||
|  | 	downloadName := repo.Name + "-" + archiveReq.GetArchiveName() | ||||||
|  |  | ||||||
|  | 	if setting.Repository.StreamArchives { | ||||||
|  | 		httplib.ServeSetHeaders(ctx.Resp, &httplib.ServeHeaderOptions{Filename: downloadName}) | ||||||
|  | 		if err := archiveReq.Stream(ctx, gitRepo, ctx.Resp); err != nil && !ctx.Written() { | ||||||
|  | 			log.Error("Archive %v streaming failed: %v", archiveReq, err) | ||||||
|  | 			ctx.HTTPError(http.StatusInternalServerError) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	archiver, err := archiveReq.Await(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error("Archive %v await failed: %v", archiveReq, err) | ||||||
|  | 		ctx.HTTPError(http.StatusInternalServerError) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rPath := archiver.RelativePath() | ||||||
|  | 	if setting.RepoArchive.Storage.ServeDirect() { | ||||||
|  | 		// If we have a signed url (S3, object storage), redirect to this directly. | ||||||
|  | 		u, err := storage.RepoArchives.URL(rPath, downloadName, ctx.Req.Method, nil) | ||||||
|  | 		if u != nil && err == nil { | ||||||
|  | 			ctx.Redirect(u.String()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fr, err := storage.RepoArchives.Open(rPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error("Archive %v open file failed: %v", archiveReq, err) | ||||||
|  | 		ctx.HTTPError(http.StatusInternalServerError) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	defer fr.Close() | ||||||
|  |  | ||||||
|  | 	ctx.ServeContent(fr, &gitea_context.ServeHeaderOptions{ | ||||||
|  | 		Filename:     downloadName, | ||||||
|  | 		LastModified: archiver.CreatedUnix.AsLocalTime(), | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user