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