mirror of
https://github.com/go-gitea/gitea
synced 2024-12-23 00:54:28 +00:00
Add github compatible tarball download API endpoints (#32572)
Fix #29654 Fix #32481
This commit is contained in:
parent
44909f6e2c
commit
703be6bf30
@ -1377,6 +1377,8 @@ func Routes() *web.Router {
|
|||||||
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
||||||
m.Delete("", repo.DeleteAvatar)
|
m.Delete("", repo.DeleteAvatar)
|
||||||
}, reqAdmin(), reqToken())
|
}, reqAdmin(), reqToken())
|
||||||
|
|
||||||
|
m.Get("/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
|
||||||
}, repoAssignment(), checkTokenPublicOnly())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||||
|
|
||||||
|
53
routers/api/v1/repo/download.go
Normal file
53
routers/api/v1/repo/download.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
archiver_service "code.gitea.io/gitea/services/repository/archiver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DownloadArchive(ctx *context.APIContext) {
|
||||||
|
var tp git.ArchiveType
|
||||||
|
switch ballType := ctx.PathParam("ball_type"); ballType {
|
||||||
|
case "tarball":
|
||||||
|
tp = git.TARGZ
|
||||||
|
case "zipball":
|
||||||
|
tp = git.ZIP
|
||||||
|
case "bundle":
|
||||||
|
tp = git.BUNDLE
|
||||||
|
default:
|
||||||
|
ctx.Error(http.StatusBadRequest, "", fmt.Sprintf("Unknown archive type: %s", ballType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.GitRepo == nil {
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Repo.GitRepo = gitRepo
|
||||||
|
defer gitRepo.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"), tp)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("NewRequest", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
archive, err := r.Await(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("archive.Await", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
download(ctx, r.GetArchiveName(), archive)
|
||||||
|
}
|
@ -301,7 +301,13 @@ func GetArchive(ctx *context.APIContext) {
|
|||||||
|
|
||||||
func archiveDownload(ctx *context.APIContext) {
|
func archiveDownload(ctx *context.APIContext) {
|
||||||
uri := ctx.PathParam("*")
|
uri := ctx.PathParam("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
ext, tp, err := archiver_service.ParseFileName(uri)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusBadRequest, "ParseFileName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
||||||
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
|
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
|
||||||
@ -327,9 +333,12 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
|
|||||||
|
|
||||||
// Add nix format link header so tarballs lock correctly:
|
// Add nix format link header so tarballs lock correctly:
|
||||||
// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
|
// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
|
||||||
ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.tar.gz?rev=%s>; rel="immutable"`,
|
ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.%s?rev=%s>; rel="immutable"`,
|
||||||
ctx.Repo.Repository.APIURL(),
|
ctx.Repo.Repository.APIURL(),
|
||||||
archiver.CommitID, archiver.CommitID))
|
archiver.CommitID,
|
||||||
|
archiver.Type.String(),
|
||||||
|
archiver.CommitID,
|
||||||
|
))
|
||||||
|
|
||||||
rPath := archiver.RelativePath()
|
rPath := archiver.RelativePath()
|
||||||
if setting.RepoArchive.Storage.ServeDirect() {
|
if setting.RepoArchive.Storage.ServeDirect() {
|
||||||
|
@ -464,7 +464,12 @@ func RedirectDownload(ctx *context.Context) {
|
|||||||
// Download an archive of a repository
|
// Download an archive of a repository
|
||||||
func Download(ctx *context.Context) {
|
func Download(ctx *context.Context) {
|
||||||
uri := ctx.PathParam("*")
|
uri := ctx.PathParam("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
ext, tp, err := archiver_service.ParseFileName(uri)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("ParseFileName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
||||||
ctx.Error(http.StatusBadRequest, err.Error())
|
ctx.Error(http.StatusBadRequest, err.Error())
|
||||||
@ -523,7 +528,12 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
|
|||||||
// kind of drop it on the floor if this is the case.
|
// kind of drop it on the floor if this is the case.
|
||||||
func InitiateDownload(ctx *context.Context) {
|
func InitiateDownload(ctx *context.Context) {
|
||||||
uri := ctx.PathParam("*")
|
uri := ctx.PathParam("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
ext, tp, err := archiver_service.ParseFileName(uri)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("ParseFileName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("archiver_service.NewRequest", err)
|
ctx.ServerError("archiver_service.NewRequest", err)
|
||||||
return
|
return
|
||||||
|
@ -67,30 +67,36 @@ func (e RepoRefNotFoundError) Is(err error) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest creates an archival request, based on the URI. The
|
func ParseFileName(uri string) (ext string, tp git.ArchiveType, err error) {
|
||||||
// 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, uri string) (*ArchiveRequest, error) {
|
|
||||||
r := &ArchiveRequest{
|
|
||||||
RepoID: repoID,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext string
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasSuffix(uri, ".zip"):
|
case strings.HasSuffix(uri, ".zip"):
|
||||||
ext = ".zip"
|
ext = ".zip"
|
||||||
r.Type = git.ZIP
|
tp = git.ZIP
|
||||||
case strings.HasSuffix(uri, ".tar.gz"):
|
case strings.HasSuffix(uri, ".tar.gz"):
|
||||||
ext = ".tar.gz"
|
ext = ".tar.gz"
|
||||||
r.Type = git.TARGZ
|
tp = git.TARGZ
|
||||||
case strings.HasSuffix(uri, ".bundle"):
|
case strings.HasSuffix(uri, ".bundle"):
|
||||||
ext = ".bundle"
|
ext = ".bundle"
|
||||||
r.Type = git.BUNDLE
|
tp = git.BUNDLE
|
||||||
default:
|
default:
|
||||||
return nil, ErrUnknownArchiveFormat{RequestFormat: uri}
|
return "", 0, ErrUnknownArchiveFormat{RequestFormat: uri}
|
||||||
|
}
|
||||||
|
return ext, tp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
r.refName = strings.TrimSuffix(uri, ext)
|
// 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, refName string, fileType git.ArchiveType) (*ArchiveRequest, error) {
|
||||||
|
if fileType < git.ZIP || fileType > git.BUNDLE {
|
||||||
|
return nil, ErrUnknownArchiveFormat{RequestFormat: fileType.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &ArchiveRequest{
|
||||||
|
RepoID: repoID,
|
||||||
|
refName: refName,
|
||||||
|
Type: fileType,
|
||||||
|
}
|
||||||
|
|
||||||
// Get corresponding commit.
|
// Get corresponding commit.
|
||||||
commitID, err := repo.ConvertToGitID(r.refName)
|
commitID, err := repo.ConvertToGitID(r.refName)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/services/contexttest"
|
"code.gitea.io/gitea/services/contexttest"
|
||||||
|
|
||||||
_ "code.gitea.io/gitea/models/actions"
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
@ -31,47 +32,47 @@ func TestArchive_Basic(t *testing.T) {
|
|||||||
contexttest.LoadGitRepo(t, ctx)
|
contexttest.LoadGitRepo(t, ctx)
|
||||||
defer ctx.Repo.GitRepo.Close()
|
defer ctx.Repo.GitRepo.Close()
|
||||||
|
|
||||||
bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
// Check a series of bogus requests.
|
// Check a series of bogus requests.
|
||||||
// Step 1, valid commit with a bad extension.
|
// Step 1, valid commit with a bad extension.
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert")
|
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, 100)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
// Step 2, missing commit.
|
// Step 2, missing commit.
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip")
|
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff", git.ZIP)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
// Step 3, doesn't look like branch/tag/commit.
|
// 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.ID, ctx.Repo.GitRepo, "db", git.ZIP)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip")
|
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master", git.ZIP)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip")
|
bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive", git.ZIP)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
// Now two valid requests, firstCommit with valid extensions.
|
// 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.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, zipReq)
|
assert.NotNil(t, zipReq)
|
||||||
|
|
||||||
tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz")
|
tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.TARGZ)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, tgzReq)
|
assert.NotNil(t, tgzReq)
|
||||||
|
|
||||||
secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip")
|
secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit, git.ZIP)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, secondReq)
|
assert.NotNil(t, secondReq)
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ func TestArchive_Basic(t *testing.T) {
|
|||||||
// Sleep two seconds to make sure the queue doesn't change.
|
// Sleep two seconds to make sure the queue doesn't change.
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// This zipReq should match what's sitting in the queue, as we haven't
|
// 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
|
// let it release yet. From the consumer's point of view, this looks like
|
||||||
@ -106,12 +107,12 @@ func TestArchive_Basic(t *testing.T) {
|
|||||||
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
||||||
// after we release it. We should trigger both the timeout and non-timeout
|
// after we release it. We should trigger both the timeout and non-timeout
|
||||||
// cases.
|
// cases.
|
||||||
timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
|
timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit, git.TARGZ)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, timedReq)
|
assert.NotNil(t, timedReq)
|
||||||
doArchive(db.DefaultContext, timedReq)
|
doArchive(db.DefaultContext, timedReq)
|
||||||
|
|
||||||
zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// Now, we're guaranteed to have released the original zipReq from the queue.
|
// 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
|
// Ensure that we don't get handed back the released entry somehow, but they
|
||||||
|
@ -59,3 +59,43 @@ func TestAPIDownloadArchive(t *testing.T) {
|
|||||||
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
|
||||||
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
|
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIDownloadArchive2(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
session := loginUser(t, user2.LowerName)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/zipball/master", user2.Name, repo.Name))
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs, err := io.ReadAll(resp.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, bs, 320)
|
||||||
|
|
||||||
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/tarball/master", user2.Name, repo.Name))
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs, err = io.ReadAll(resp.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, bs, 266)
|
||||||
|
|
||||||
|
// Must return a link to a commit ID as the "immutable" archive link
|
||||||
|
linkHeaderRe := regexp.MustCompile(`^<(https?://.*/api/v1/repos/user2/repo1/archive/[a-f0-9]+\.tar\.gz.*)>; rel="immutable"$`)
|
||||||
|
m := linkHeaderRe.FindStringSubmatch(resp.Header().Get("Link"))
|
||||||
|
assert.NotEmpty(t, m[1])
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", m[1]).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs2, err := io.ReadAll(resp.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// The locked URL should give the same bytes as the non-locked one
|
||||||
|
assert.EqualValues(t, bs, bs2)
|
||||||
|
|
||||||
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/bundle/master", user2.Name, repo.Name))
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs, err = io.ReadAll(resp.Body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, bs, 382)
|
||||||
|
|
||||||
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user