mirror of
https://github.com/go-gitea/gitea.git
synced 2025-09-26 18:38:14 +00: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:
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -17,11 +18,13 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// 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
|
||||
// will occur in a separate goroutine, as this phase may take a while to
|
||||
// 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()
|
||||
|
||||
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() {
|
||||
if r := recover(); r != nil {
|
||||
done <- fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if archiver.Type == git.ArchiveBundle {
|
||||
err = gitRepo.CreateBundle(
|
||||
ctx,
|
||||
archiver.CommitID,
|
||||
w,
|
||||
)
|
||||
} else {
|
||||
err = gitRepo.CreateArchive(
|
||||
ctx,
|
||||
archiver.Type,
|
||||
w,
|
||||
setting.Repository.PrefixArchiveFiles,
|
||||
archiver.CommitID,
|
||||
)
|
||||
}
|
||||
err := archiveReq.Stream(ctx, gitRepo, w)
|
||||
_ = w.CloseWithError(err)
|
||||
done <- err
|
||||
}(done, w, archiver, gitRepo)
|
||||
}(done, w, r, gitRepo)
|
||||
|
||||
// TODO: add lfs data to zip
|
||||
// TODO: add submodule data to zip
|
||||
@@ -338,3 +346,54 @@ func DeleteRepositoryArchives(ctx context.Context) error {
|
||||
}
|
||||
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