Add ability to download subpath archive (#36371)

closes: https://github.com/go-gitea/gitea/issues/4478

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
TheFox0x7
2026-01-16 10:31:12 +01:00
committed by GitHub
parent 67e75f30a8
commit 69c5921d71
18 changed files with 230 additions and 134 deletions
+16 -6
View File
@@ -8,25 +8,35 @@ import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/util"
"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, ctx.Repo.GitRepo, reqFileName)
func serveRepoArchive(ctx *context.APIContext, reqFileName string, paths []string) {
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, reqFileName, paths)
if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err)
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
} else if errors.Is(err, util.ErrNotExist) {
ctx.APIError(http.StatusNotFound, err)
} else {
ctx.APIErrorInternal(err)
}
return
}
archiver_service.ServeRepoArchive(ctx.Base, aReq)
err = archiver_service.ServeRepoArchive(ctx.Base, aReq)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.APIError(http.StatusBadRequest, err)
} else {
ctx.APIErrorInternal(err)
}
}
}
// DownloadArchive is the GitHub-compatible endpoint to download repository archives
// TODO: The API document is missing: Add github compatible tarball download API endpoints (#32572)
func DownloadArchive(ctx *context.APIContext) {
var tp repo_model.ArchiveType
switch ballType := ctx.PathParam("ball_type"); ballType {
@@ -40,5 +50,5 @@ func DownloadArchive(ctx *context.APIContext) {
ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType)
return
}
serveRepoArchive(ctx, ctx.PathParam("*")+"."+tp.String())
serveRepoArchive(ctx, ctx.PathParam("*")+"."+tp.String(), ctx.FormStrings("path"))
}
+8 -2
View File
@@ -273,13 +273,19 @@ func GetArchive(ctx *context.APIContext) {
// description: the git reference for download with attached archive format (e.g. master.zip)
// type: string
// required: true
// - name: path
// in: query
// type: array
// items:
// type: string
// description: subpath of the repository to download
// collectionFormat: multi
// responses:
// 200:
// description: success
// "404":
// "$ref": "#/responses/notFound"
serveRepoArchive(ctx, ctx.PathParam("*"))
serveRepoArchive(ctx, ctx.PathParam("*"), ctx.FormStrings("path"))
}
// GetEditorconfig get editor config of a repository
+14 -6
View File
@@ -364,31 +364,39 @@ func RedirectDownload(ctx *context.Context) {
// Download an archive of a repository
func Download(ctx *context.Context) {
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"))
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"), ctx.FormStrings("path"))
if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.HTTPError(http.StatusBadRequest, err.Error())
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
} else if errors.Is(err, util.ErrNotExist) {
ctx.HTTPError(http.StatusNotFound, err.Error())
} else {
ctx.ServerError("archiver_service.NewRequest", err)
}
return
}
archiver_service.ServeRepoArchive(ctx.Base, aReq)
err = archiver_service.ServeRepoArchive(ctx.Base, aReq)
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
ctx.HTTPError(http.StatusBadRequest, err.Error())
} else {
ctx.ServerError("archiver_service.ServeRepoArchive", err)
}
}
}
// InitiateDownload will enqueue an archival request, as needed. It may submit
// a request that's already in-progress, but the archiver service will just
// kind of drop it on the floor if this is the case.
func InitiateDownload(ctx *context.Context) {
if setting.Repository.StreamArchives {
paths := ctx.FormStrings("path")
if setting.Repository.StreamArchives || len(paths) > 0 {
ctx.JSON(http.StatusOK, map[string]any{
"complete": true,
})
return
}
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"))
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("*"), paths)
if err != nil {
ctx.HTTPError(http.StatusBadRequest, "invalid archive request")
return