|
|
|
|
@ -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(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|