88 "errors"
99 "fmt"
1010 "io"
11+ "net/http"
1112 "os"
1213 "strings"
1314 "time"
@@ -17,11 +18,13 @@ import (
1718 "code.gitea.io/gitea/modules/git"
1819 "code.gitea.io/gitea/modules/gitrepo"
1920 "code.gitea.io/gitea/modules/graceful"
21+ "code.gitea.io/gitea/modules/httplib"
2022 "code.gitea.io/gitea/modules/log"
2123 "code.gitea.io/gitea/modules/process"
2224 "code.gitea.io/gitea/modules/queue"
2325 "code.gitea.io/gitea/modules/setting"
2426 "code.gitea.io/gitea/modules/storage"
27+ gitea_context "code.gitea.io/gitea/services/context"
2528)
2629
2730// ArchiveRequest defines the parameters of an archive request, which notably
@@ -138,6 +141,25 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver
138141 }
139142}
140143
144+ // Stream satisfies the ArchiveRequest being passed in. Processing
145+ // will occur directly in this routine.
146+ func (aReq * ArchiveRequest ) Stream (ctx context.Context , gitRepo * git.Repository , w io.Writer ) error {
147+ if aReq .Type == git .ArchiveBundle {
148+ return gitRepo .CreateBundle (
149+ ctx ,
150+ aReq .CommitID ,
151+ w ,
152+ )
153+ }
154+ return gitRepo .CreateArchive (
155+ ctx ,
156+ aReq .Type ,
157+ w ,
158+ setting .Repository .PrefixArchiveFiles ,
159+ aReq .CommitID ,
160+ )
161+ }
162+
141163// doArchive satisfies the ArchiveRequest being passed in. Processing
142164// will occur in a separate goroutine, as this phase may take a while to
143165// complete. If the archive already exists, doArchive will not do
@@ -204,31 +226,17 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
204226 }
205227 defer gitRepo .Close ()
206228
207- go func (done chan error , w * io.PipeWriter , archiver * repo_model. RepoArchiver , gitRepo * git.Repository ) {
229+ go func (done chan error , w * io.PipeWriter , archiveReq * ArchiveRequest , gitRepo * git.Repository ) {
208230 defer func () {
209231 if r := recover (); r != nil {
210232 done <- fmt .Errorf ("%v" , r )
211233 }
212234 }()
213235
214- if archiver .Type == git .ArchiveBundle {
215- err = gitRepo .CreateBundle (
216- ctx ,
217- archiver .CommitID ,
218- w ,
219- )
220- } else {
221- err = gitRepo .CreateArchive (
222- ctx ,
223- archiver .Type ,
224- w ,
225- setting .Repository .PrefixArchiveFiles ,
226- archiver .CommitID ,
227- )
228- }
236+ err := archiveReq .Stream (ctx , gitRepo , w )
229237 _ = w .CloseWithError (err )
230238 done <- err
231- }(done , w , archiver , gitRepo )
239+ }(done , w , r , gitRepo )
232240
233241 // TODO: add lfs data to zip
234242 // TODO: add submodule data to zip
@@ -338,3 +346,54 @@ func DeleteRepositoryArchives(ctx context.Context) error {
338346 }
339347 return storage .Clean (storage .RepoArchives )
340348}
349+
350+ func ServeRepoArchive (ctx * gitea_context.Base , repo * repo_model.Repository , gitRepo * git.Repository , archiveReq * ArchiveRequest ) {
351+ // Add nix format link header so tarballs lock correctly:
352+ // https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
353+ ctx .Resp .Header ().Add ("Link" , fmt .Sprintf (`<%s/archive/%s.%s?rev=%s>; rel="immutable"` ,
354+ repo .APIURL (),
355+ archiveReq .CommitID ,
356+ archiveReq .Type .String (),
357+ archiveReq .CommitID ,
358+ ))
359+ downloadName := repo .Name + "-" + archiveReq .GetArchiveName ()
360+
361+ if setting .Repository .StreamArchives {
362+ httplib .ServeSetHeaders (ctx .Resp , & httplib.ServeHeaderOptions {Filename : downloadName })
363+ if err := archiveReq .Stream (ctx , gitRepo , ctx .Resp ); err != nil && ! ctx .Written () {
364+ log .Error ("Archive %v streaming failed: %v" , archiveReq , err )
365+ ctx .HTTPError (http .StatusInternalServerError )
366+ }
367+ return
368+ }
369+
370+ archiver , err := archiveReq .Await (ctx )
371+ if err != nil {
372+ log .Error ("Archive %v await failed: %v" , archiveReq , err )
373+ ctx .HTTPError (http .StatusInternalServerError )
374+ return
375+ }
376+
377+ rPath := archiver .RelativePath ()
378+ if setting .RepoArchive .Storage .ServeDirect () {
379+ // If we have a signed url (S3, object storage), redirect to this directly.
380+ u , err := storage .RepoArchives .URL (rPath , downloadName , ctx .Req .Method , nil )
381+ if u != nil && err == nil {
382+ ctx .Redirect (u .String ())
383+ return
384+ }
385+ }
386+
387+ fr , err := storage .RepoArchives .Open (rPath )
388+ if err != nil {
389+ log .Error ("Archive %v open file failed: %v" , archiveReq , err )
390+ ctx .HTTPError (http .StatusInternalServerError )
391+ return
392+ }
393+ defer fr .Close ()
394+
395+ ctx .ServeContent (fr , & gitea_context.ServeHeaderOptions {
396+ Filename : downloadName ,
397+ LastModified : archiver .CreatedUnix .AsLocalTime (),
398+ })
399+ }
0 commit comments