8
8
"errors"
9
9
"fmt"
10
10
"io"
11
+ "net/http"
11
12
"os"
12
13
"strings"
13
14
"time"
@@ -17,11 +18,13 @@ import (
17
18
"code.gitea.io/gitea/modules/git"
18
19
"code.gitea.io/gitea/modules/gitrepo"
19
20
"code.gitea.io/gitea/modules/graceful"
21
+ "code.gitea.io/gitea/modules/httplib"
20
22
"code.gitea.io/gitea/modules/log"
21
23
"code.gitea.io/gitea/modules/process"
22
24
"code.gitea.io/gitea/modules/queue"
23
25
"code.gitea.io/gitea/modules/setting"
24
26
"code.gitea.io/gitea/modules/storage"
27
+ gitea_context "code.gitea.io/gitea/services/context"
25
28
)
26
29
27
30
// ArchiveRequest defines the parameters of an archive request, which notably
@@ -138,6 +141,25 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver
138
141
}
139
142
}
140
143
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
+
141
163
// doArchive satisfies the ArchiveRequest being passed in. Processing
142
164
// will occur in a separate goroutine, as this phase may take a while to
143
165
// complete. If the archive already exists, doArchive will not do
@@ -204,31 +226,17 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
204
226
}
205
227
defer gitRepo .Close ()
206
228
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 ) {
208
230
defer func () {
209
231
if r := recover (); r != nil {
210
232
done <- fmt .Errorf ("%v" , r )
211
233
}
212
234
}()
213
235
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 )
229
237
_ = w .CloseWithError (err )
230
238
done <- err
231
- }(done , w , archiver , gitRepo )
239
+ }(done , w , r , gitRepo )
232
240
233
241
// TODO: add lfs data to zip
234
242
// TODO: add submodule data to zip
@@ -338,3 +346,54 @@ func DeleteRepositoryArchives(ctx context.Context) error {
338
346
}
339
347
return storage .Clean (storage .RepoArchives )
340
348
}
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