mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 19:38:23 +00:00 
			
		
		
		
	Move archive function to repo_model and gitrepo (#35514)
This commit is contained in:
		| @@ -1,7 +1,7 @@ | ||||
| // Copyright 2025 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
| 
 | ||||
| package git | ||||
| package repo | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| @@ -11,7 +11,6 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/timeutil" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
|  | ||||
| @@ -27,11 +26,46 @@ const ( | ||||
| 	ArchiverReady             // it's ready | ||||
| ) | ||||
|  | ||||
| // ArchiveType archive types | ||||
| type ArchiveType int | ||||
|  | ||||
| const ( | ||||
| 	ArchiveUnknown ArchiveType = iota | ||||
| 	ArchiveZip                 // 1 | ||||
| 	ArchiveTarGz               // 2 | ||||
| 	ArchiveBundle              // 3 | ||||
| ) | ||||
|  | ||||
| // String converts an ArchiveType to string: the extension of the archive file without prefix dot | ||||
| func (a ArchiveType) String() string { | ||||
| 	switch a { | ||||
| 	case ArchiveZip: | ||||
| 		return "zip" | ||||
| 	case ArchiveTarGz: | ||||
| 		return "tar.gz" | ||||
| 	case ArchiveBundle: | ||||
| 		return "bundle" | ||||
| 	} | ||||
| 	return "unknown" | ||||
| } | ||||
|  | ||||
| func SplitArchiveNameType(s string) (string, ArchiveType) { | ||||
| 	switch { | ||||
| 	case strings.HasSuffix(s, ".zip"): | ||||
| 		return strings.TrimSuffix(s, ".zip"), ArchiveZip | ||||
| 	case strings.HasSuffix(s, ".tar.gz"): | ||||
| 		return strings.TrimSuffix(s, ".tar.gz"), ArchiveTarGz | ||||
| 	case strings.HasSuffix(s, ".bundle"): | ||||
| 		return strings.TrimSuffix(s, ".bundle"), ArchiveBundle | ||||
| 	} | ||||
| 	return s, ArchiveUnknown | ||||
| } | ||||
|  | ||||
| // RepoArchiver represents all archivers | ||||
| type RepoArchiver struct { //revive:disable-line:exported | ||||
| 	ID          int64           `xorm:"pk autoincr"` | ||||
| 	RepoID      int64           `xorm:"index unique(s)"` | ||||
| 	Type        git.ArchiveType `xorm:"unique(s)"` | ||||
| 	ID          int64       `xorm:"pk autoincr"` | ||||
| 	RepoID      int64       `xorm:"index unique(s)"` | ||||
| 	Type        ArchiveType `xorm:"unique(s)"` | ||||
| 	Status      ArchiverStatus | ||||
| 	CommitID    string             `xorm:"VARCHAR(64) unique(s)"` | ||||
| 	CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"` | ||||
| @@ -56,15 +90,15 @@ func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid repo id") | ||||
| 	} | ||||
| 	commitID, archiveType := git.SplitArchiveNameType(parts[2]) | ||||
| 	if archiveType == git.ArchiveUnknown { | ||||
| 	commitID, archiveType := SplitArchiveNameType(parts[2]) | ||||
| 	if archiveType == ArchiveUnknown { | ||||
| 		return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid archive type") | ||||
| 	} | ||||
| 	return &RepoArchiver{RepoID: repoID, CommitID: commitID, Type: archiveType}, nil | ||||
| } | ||||
|  | ||||
| // GetRepoArchiver get an archiver | ||||
| func GetRepoArchiver(ctx context.Context, repoID int64, tp git.ArchiveType, commitID string) (*RepoArchiver, error) { | ||||
| func GetRepoArchiver(ctx context.Context, repoID int64, tp ArchiveType, commitID string) (*RepoArchiver, error) { | ||||
| 	var archiver RepoArchiver | ||||
| 	has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("`type`=?", tp).And("commit_id=?", commitID).Get(&archiver) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -12,14 +12,12 @@ import ( | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/git/gitcmd" | ||||
| 	"code.gitea.io/gitea/modules/proxy" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| ) | ||||
|  | ||||
| // GPGSettings represents the default GPG settings for this repository | ||||
| @@ -242,43 +240,3 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error | ||||
| 	commitTime := strings.TrimSpace(stdout) | ||||
| 	return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime) | ||||
| } | ||||
|  | ||||
| // CreateBundle create bundle content to the target path | ||||
| func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error { | ||||
| 	tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) | ||||
| 	_, _, err = gitcmd.NewCommand("init", "--bare").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, _, err = gitcmd.NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, _, err = gitcmd.NewCommand("branch", "-m", "bundle").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	tmpFile := filepath.Join(tmp, "bundle") | ||||
| 	_, _, err = gitcmd.NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	fi, err := os.Open(tmpFile) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer fi.Close() | ||||
|  | ||||
| 	_, err = io.Copy(out, fi) | ||||
| 	return err | ||||
| } | ||||
|   | ||||
| @@ -1,75 +0,0 @@ | ||||
| // Copyright 2015 The Gogs Authors. All rights reserved. | ||||
| // Copyright 2020 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package git | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/git/gitcmd" | ||||
| ) | ||||
|  | ||||
| // ArchiveType archive types | ||||
| type ArchiveType int | ||||
|  | ||||
| const ( | ||||
| 	ArchiveUnknown ArchiveType = iota | ||||
| 	ArchiveZip                 // 1 | ||||
| 	ArchiveTarGz               // 2 | ||||
| 	ArchiveBundle              // 3 | ||||
| ) | ||||
|  | ||||
| // String converts an ArchiveType to string: the extension of the archive file without prefix dot | ||||
| func (a ArchiveType) String() string { | ||||
| 	switch a { | ||||
| 	case ArchiveZip: | ||||
| 		return "zip" | ||||
| 	case ArchiveTarGz: | ||||
| 		return "tar.gz" | ||||
| 	case ArchiveBundle: | ||||
| 		return "bundle" | ||||
| 	} | ||||
| 	return "unknown" | ||||
| } | ||||
|  | ||||
| func SplitArchiveNameType(s string) (string, ArchiveType) { | ||||
| 	switch { | ||||
| 	case strings.HasSuffix(s, ".zip"): | ||||
| 		return strings.TrimSuffix(s, ".zip"), ArchiveZip | ||||
| 	case strings.HasSuffix(s, ".tar.gz"): | ||||
| 		return strings.TrimSuffix(s, ".tar.gz"), ArchiveTarGz | ||||
| 	case strings.HasSuffix(s, ".bundle"): | ||||
| 		return strings.TrimSuffix(s, ".bundle"), ArchiveBundle | ||||
| 	} | ||||
| 	return s, ArchiveUnknown | ||||
| } | ||||
|  | ||||
| // CreateArchive create archive content to the target path | ||||
| func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, target io.Writer, usePrefix bool, commitID string) error { | ||||
| 	if format.String() == "unknown" { | ||||
| 		return fmt.Errorf("unknown format: %v", format) | ||||
| 	} | ||||
|  | ||||
| 	cmd := gitcmd.NewCommand("archive") | ||||
| 	if usePrefix { | ||||
| 		cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/") | ||||
| 	} | ||||
| 	cmd.AddOptionFormat("--format=%s", format.String()) | ||||
| 	cmd.AddDynamicArguments(commitID) | ||||
|  | ||||
| 	var stderr strings.Builder | ||||
| 	err := cmd.Run(ctx, &gitcmd.RunOpts{ | ||||
| 		Dir:    repo.Path, | ||||
| 		Stdout: target, | ||||
| 		Stderr: &stderr, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return gitcmd.ConcatenateError(err, stderr.String()) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										81
									
								
								modules/gitrepo/archive.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								modules/gitrepo/archive.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| // Copyright 2025 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package gitrepo | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/git/gitcmd" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| ) | ||||
|  | ||||
| // CreateArchive create archive content to the target path | ||||
| func CreateArchive(ctx context.Context, repo Repository, format string, target io.Writer, usePrefix bool, commitID string) error { | ||||
| 	if format == "unknown" { | ||||
| 		return fmt.Errorf("unknown format: %v", format) | ||||
| 	} | ||||
|  | ||||
| 	cmd := gitcmd.NewCommand("archive") | ||||
| 	if usePrefix { | ||||
| 		cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.RelativePath(), ".git"))+"/") | ||||
| 	} | ||||
| 	cmd.AddOptionFormat("--format=%s", format) | ||||
| 	cmd.AddDynamicArguments(commitID) | ||||
|  | ||||
| 	var stderr strings.Builder | ||||
| 	err := cmd.Run(ctx, &gitcmd.RunOpts{ | ||||
| 		Dir:    repoPath(repo), | ||||
| 		Stdout: target, | ||||
| 		Stderr: &stderr, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return gitcmd.ConcatenateError(err, stderr.String()) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CreateBundle create bundle content to the target path | ||||
| func CreateBundle(ctx context.Context, repo Repository, commit string, out io.Writer) error { | ||||
| 	tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repoPath(repo), "objects")) | ||||
| 	_, _, err = gitcmd.NewCommand("init", "--bare").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, _, err = gitcmd.NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, _, err = gitcmd.NewCommand("branch", "-m", "bundle").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	tmpFile := filepath.Join(tmp, "bundle") | ||||
| 	_, _, err = gitcmd.NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	fi, err := os.Open(tmpFile) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer fi.Close() | ||||
|  | ||||
| 	_, err = io.Copy(out, fi) | ||||
| 	return err | ||||
| } | ||||
| @@ -7,13 +7,13 @@ import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	repo_model "code.gitea.io/gitea/models/repo" | ||||
| 	"code.gitea.io/gitea/services/context" | ||||
| 	archiver_service "code.gitea.io/gitea/services/repository/archiver" | ||||
| ) | ||||
|  | ||||
| func serveRepoArchive(ctx *context.APIContext, reqFileName string) { | ||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, reqFileName) | ||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, reqFileName) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { | ||||
| 			ctx.APIError(http.StatusBadRequest, err) | ||||
| @@ -24,18 +24,18 @@ func serveRepoArchive(ctx *context.APIContext, reqFileName string) { | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	archiver_service.ServeRepoArchive(ctx.Base, ctx.Repo.Repository, ctx.Repo.GitRepo, aReq) | ||||
| 	archiver_service.ServeRepoArchive(ctx.Base, aReq) | ||||
| } | ||||
|  | ||||
| func DownloadArchive(ctx *context.APIContext) { | ||||
| 	var tp git.ArchiveType | ||||
| 	var tp repo_model.ArchiveType | ||||
| 	switch ballType := ctx.PathParam("ball_type"); ballType { | ||||
| 	case "tarball": | ||||
| 		tp = git.ArchiveTarGz | ||||
| 		tp = repo_model.ArchiveTarGz | ||||
| 	case "zipball": | ||||
| 		tp = git.ArchiveZip | ||||
| 		tp = repo_model.ArchiveZip | ||||
| 	case "bundle": | ||||
| 		tp = git.ArchiveBundle | ||||
| 		tp = repo_model.ArchiveBundle | ||||
| 	default: | ||||
| 		ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType) | ||||
| 		return | ||||
|   | ||||
| @@ -364,7 +364,7 @@ func RedirectDownload(ctx *context.Context) { | ||||
|  | ||||
| // Download an archive of a repository | ||||
| func Download(ctx *context.Context) { | ||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")) | ||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*")) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { | ||||
| 			ctx.HTTPError(http.StatusBadRequest, err.Error()) | ||||
| @@ -375,7 +375,7 @@ func Download(ctx *context.Context) { | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	archiver_service.ServeRepoArchive(ctx.Base, ctx.Repo.Repository, ctx.Repo.GitRepo, aReq) | ||||
| 	archiver_service.ServeRepoArchive(ctx.Base, aReq) | ||||
| } | ||||
|  | ||||
| // InitiateDownload will enqueue an archival request, as needed.  It may submit | ||||
| @@ -388,7 +388,7 @@ func InitiateDownload(ctx *context.Context) { | ||||
| 		}) | ||||
| 		return | ||||
| 	} | ||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")) | ||||
| 	aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*")) | ||||
| 	if err != nil { | ||||
| 		ctx.HTTPError(http.StatusBadRequest, "invalid archive request") | ||||
| 		return | ||||
| @@ -398,7 +398,7 @@ func InitiateDownload(ctx *context.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	archiver, err := repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID) | ||||
| 	archiver, err := repo_model.GetRepoArchiver(ctx, aReq.Repo.ID, aReq.Type, aReq.CommitID) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("archiver_service.StartArchive", err) | ||||
| 		return | ||||
|   | ||||
| @@ -33,8 +33,8 @@ import ( | ||||
| // This is entirely opaque to external entities, though, and mostly used as a | ||||
| // handle elsewhere. | ||||
| type ArchiveRequest struct { | ||||
| 	RepoID   int64 | ||||
| 	Type     git.ArchiveType | ||||
| 	Repo     *repo_model.Repository | ||||
| 	Type     repo_model.ArchiveType | ||||
| 	CommitID string | ||||
|  | ||||
| 	archiveRefShortName string // the ref short name to download the archive, for example: "master", "v1.0.0", "commit id" | ||||
| @@ -74,20 +74,20 @@ func (e RepoRefNotFoundError) Is(err error) bool { | ||||
| // NewRequest creates an archival request, based on the URI.  The | ||||
| // resulting ArchiveRequest is suitable for being passed to Await() | ||||
| // if it's determined that the request still needs to be satisfied. | ||||
| func NewRequest(repoID int64, repo *git.Repository, archiveRefExt string) (*ArchiveRequest, error) { | ||||
| func NewRequest(repo *repo_model.Repository, gitRepo *git.Repository, archiveRefExt string) (*ArchiveRequest, error) { | ||||
| 	// here the archiveRefShortName is not a clear ref, it could be a tag, branch or commit id | ||||
| 	archiveRefShortName, archiveType := git.SplitArchiveNameType(archiveRefExt) | ||||
| 	if archiveType == git.ArchiveUnknown { | ||||
| 	archiveRefShortName, archiveType := repo_model.SplitArchiveNameType(archiveRefExt) | ||||
| 	if archiveType == repo_model.ArchiveUnknown { | ||||
| 		return nil, ErrUnknownArchiveFormat{archiveRefExt} | ||||
| 	} | ||||
|  | ||||
| 	// Get corresponding commit. | ||||
| 	commitID, err := repo.ConvertToGitID(archiveRefShortName) | ||||
| 	commitID, err := gitRepo.ConvertToGitID(archiveRefShortName) | ||||
| 	if err != nil { | ||||
| 		return nil, RepoRefNotFoundError{RefShortName: archiveRefShortName} | ||||
| 	} | ||||
|  | ||||
| 	r := &ArchiveRequest{RepoID: repoID, archiveRefShortName: archiveRefShortName, Type: archiveType} | ||||
| 	r := &ArchiveRequest{Repo: repo, archiveRefShortName: archiveRefShortName, Type: archiveType} | ||||
| 	r.CommitID = commitID.String() | ||||
| 	return r, nil | ||||
| } | ||||
| @@ -105,7 +105,7 @@ func (aReq *ArchiveRequest) GetArchiveName() string { | ||||
| // context is cancelled/times out a started archiver will still continue to run | ||||
| // in the background. | ||||
| func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver, error) { | ||||
| 	archiver, err := repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID) | ||||
| 	archiver, err := repo_model.GetRepoArchiver(ctx, aReq.Repo.ID, aReq.Type, aReq.CommitID) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("models.GetRepoArchiver: %w", err) | ||||
| 	} | ||||
| @@ -130,7 +130,7 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver | ||||
| 		case <-ctx.Done(): | ||||
| 			return nil, ctx.Err() | ||||
| 		case <-poll.C: | ||||
| 			archiver, err = repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID) | ||||
| 			archiver, err = repo_model.GetRepoArchiver(ctx, aReq.Repo.ID, aReq.Type, aReq.CommitID) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("repo_model.GetRepoArchiver: %w", err) | ||||
| 			} | ||||
| @@ -143,17 +143,19 @@ 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( | ||||
| func (aReq *ArchiveRequest) Stream(ctx context.Context, w io.Writer) error { | ||||
| 	if aReq.Type == repo_model.ArchiveBundle { | ||||
| 		return gitrepo.CreateBundle( | ||||
| 			ctx, | ||||
| 			aReq.Repo, | ||||
| 			aReq.CommitID, | ||||
| 			w, | ||||
| 		) | ||||
| 	} | ||||
| 	return gitRepo.CreateArchive( | ||||
| 	return gitrepo.CreateArchive( | ||||
| 		ctx, | ||||
| 		aReq.Type, | ||||
| 		aReq.Repo, | ||||
| 		aReq.Type.String(), | ||||
| 		w, | ||||
| 		setting.Repository.PrefixArchiveFiles, | ||||
| 		aReq.CommitID, | ||||
| @@ -167,10 +169,10 @@ func (aReq *ArchiveRequest) Stream(ctx context.Context, gitRepo *git.Repository, | ||||
| // being returned for completion, as it may be different than the one they passed | ||||
| // in. | ||||
| func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver, error) { | ||||
| 	ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("ArchiveRequest[%d]: %s", r.RepoID, r.GetArchiveName())) | ||||
| 	ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("ArchiveRequest[%s]: %s", r.Repo.FullName(), r.GetArchiveName())) | ||||
| 	defer finished() | ||||
|  | ||||
| 	archiver, err := repo_model.GetRepoArchiver(ctx, r.RepoID, r.Type, r.CommitID) | ||||
| 	archiver, err := repo_model.GetRepoArchiver(ctx, r.Repo.ID, r.Type, r.CommitID) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -183,7 +185,7 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver | ||||
| 		} | ||||
| 	} else { | ||||
| 		archiver = &repo_model.RepoArchiver{ | ||||
| 			RepoID:   r.RepoID, | ||||
| 			RepoID:   r.Repo.ID, | ||||
| 			Type:     r.Type, | ||||
| 			CommitID: r.CommitID, | ||||
| 			Status:   repo_model.ArchiverGenerating, | ||||
| @@ -215,28 +217,18 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver | ||||
| 		_ = rd.Close() | ||||
| 	}() | ||||
| 	done := make(chan error, 1) // Ensure that there is some capacity which will ensure that the goroutine below can always finish | ||||
| 	repo, err := repo_model.GetRepositoryByID(ctx, archiver.RepoID) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("archiver.LoadRepo failed: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	gitRepo, err := gitrepo.OpenRepository(ctx, repo) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer gitRepo.Close() | ||||
|  | ||||
| 	go func(done chan error, w *io.PipeWriter, archiveReq *ArchiveRequest, gitRepo *git.Repository) { | ||||
| 	go func(done chan error, w *io.PipeWriter, archiveReq *ArchiveRequest) { | ||||
| 		defer func() { | ||||
| 			if r := recover(); r != nil { | ||||
| 				done <- fmt.Errorf("%v", r) | ||||
| 			} | ||||
| 		}() | ||||
|  | ||||
| 		err := archiveReq.Stream(ctx, gitRepo, w) | ||||
| 		err := archiveReq.Stream(ctx, w) | ||||
| 		_ = w.CloseWithError(err) | ||||
| 		done <- err | ||||
| 	}(done, w, r, gitRepo) | ||||
| 	}(done, w, r) | ||||
|  | ||||
| 	// TODO: add lfs data to zip | ||||
| 	// TODO: add submodule data to zip | ||||
| @@ -347,20 +339,20 @@ 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) { | ||||
| func ServeRepoArchive(ctx *gitea_context.Base, 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.Repo.APIURL(), | ||||
| 		archiveReq.CommitID, | ||||
| 		archiveReq.Type.String(), | ||||
| 		archiveReq.CommitID, | ||||
| 	)) | ||||
| 	downloadName := repo.Name + "-" + archiveReq.GetArchiveName() | ||||
| 	downloadName := archiveReq.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() { | ||||
| 		if err := archiveReq.Stream(ctx, ctx.Resp); err != nil && !ctx.Written() { | ||||
| 			log.Error("Archive %v streaming failed: %v", archiveReq, err) | ||||
| 			ctx.HTTPError(http.StatusInternalServerError) | ||||
| 		} | ||||
|   | ||||
| @@ -29,47 +29,47 @@ func TestArchive_Basic(t *testing.T) { | ||||
| 	contexttest.LoadGitRepo(t, ctx) | ||||
| 	defer ctx.Repo.GitRepo.Close() | ||||
|  | ||||
| 	bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	bogusReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotNil(t, bogusReq) | ||||
| 	assert.Equal(t, firstCommit+".zip", bogusReq.GetArchiveName()) | ||||
|  | ||||
| 	// Check a series of bogus requests. | ||||
| 	// Step 1, valid commit with a bad extension. | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".unknown") | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".unknown") | ||||
| 	assert.Error(t, err) | ||||
| 	assert.Nil(t, bogusReq) | ||||
|  | ||||
| 	// Step 2, missing commit. | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip") | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "dbffff.zip") | ||||
| 	assert.Error(t, err) | ||||
| 	assert.Nil(t, bogusReq) | ||||
|  | ||||
| 	// Step 3, doesn't look like branch/tag/commit. | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip") | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "db.zip") | ||||
| 	assert.Error(t, err) | ||||
| 	assert.Nil(t, bogusReq) | ||||
|  | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip") | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "master.zip") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotNil(t, bogusReq) | ||||
| 	assert.Equal(t, "master.zip", bogusReq.GetArchiveName()) | ||||
|  | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip") | ||||
| 	bogusReq, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, "test/archive.zip") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotNil(t, bogusReq) | ||||
| 	assert.Equal(t, "test-archive.zip", bogusReq.GetArchiveName()) | ||||
|  | ||||
| 	// Now two valid requests, firstCommit with valid extensions. | ||||
| 	zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	zipReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotNil(t, zipReq) | ||||
|  | ||||
| 	tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz") | ||||
| 	tgzReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".tar.gz") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotNil(t, tgzReq) | ||||
|  | ||||
| 	secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".bundle") | ||||
| 	secondReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".bundle") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotNil(t, secondReq) | ||||
|  | ||||
| @@ -89,7 +89,7 @@ func TestArchive_Basic(t *testing.T) { | ||||
| 	// Sleep two seconds to make sure the queue doesn't change. | ||||
| 	time.Sleep(2 * time.Second) | ||||
|  | ||||
| 	zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	zipReq2, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	assert.NoError(t, err) | ||||
| 	// This zipReq should match what's sitting in the queue, as we haven't | ||||
| 	// let it release yet.  From the consumer's point of view, this looks like | ||||
| @@ -104,12 +104,12 @@ func TestArchive_Basic(t *testing.T) { | ||||
| 	// Now we'll submit a request and TimedWaitForCompletion twice, before and | ||||
| 	// after we release it.  We should trigger both the timeout and non-timeout | ||||
| 	// cases. | ||||
| 	timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz") | ||||
| 	timedReq, err := NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, secondCommit+".tar.gz") | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotNil(t, timedReq) | ||||
| 	doArchive(t.Context(), timedReq) | ||||
|  | ||||
| 	zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	zipReq2, err = NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, firstCommit+".zip") | ||||
| 	assert.NoError(t, err) | ||||
| 	// Now, we're guaranteed to have released the original zipReq from the queue. | ||||
| 	// Ensure that we don't get handed back the released entry somehow, but they | ||||
|   | ||||
		Reference in New Issue
	
	Block a user