From f9bd54279d467f6c539648af4b9d7933c16fdb9c Mon Sep 17 00:00:00 2001 From: lhchavez Date: Mon, 13 Dec 2021 07:12:43 -0800 Subject: [PATCH] Support returning a tarball from the archive (#21) We supported .zip files, but now we also support tarballs! --- browser.go | 80 ++++++++++++++++++++++++++++++++++++++++--------- browser_test.go | 49 +++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 15 deletions(-) diff --git a/browser.go b/browser.go index 2bc45dc..e30a85b 100644 --- a/browser.go +++ b/browser.go @@ -1,12 +1,15 @@ package githttp import ( + "archive/tar" "archive/zip" "bytes" + "compress/gzip" "context" "encoding/base64" "encoding/json" "fmt" + "io" "net/http" "path" "strconv" @@ -461,6 +464,48 @@ func handleLog( return result, nil } +type archive interface { + Close() error + Create(path string, size int64) (io.Writer, error) +} + +type zipArchive zip.Writer + +func (a *zipArchive) Close() error { + return (*zip.Writer)(a).Close() +} + +func (a *zipArchive) Create(path string, size int64) (io.Writer, error) { + return (*zip.Writer)(a).CreateHeader(&zip.FileHeader{ + Name: path, + }) +} + +type tarArchive tar.Writer + +func (a *tarArchive) Close() error { + return (*tar.Writer)(a).Close() +} + +func (a *tarArchive) Create(path string, size int64) (io.Writer, error) { + hdr := &tar.Header{ + Name: path, + Size: size, + } + if strings.HasSuffix(path, "/") { + hdr.Typeflag = tar.TypeDir + hdr.Mode = 0o755 + } else { + hdr.Typeflag = tar.TypeReg + hdr.Mode = 0o644 + } + err := (*tar.Writer)(a).WriteHeader(hdr) + if err != nil { + return nil, err + } + return (*tar.Writer)(a), nil +} + func handleArchive( ctx context.Context, repository *git.Repository, @@ -480,7 +525,8 @@ func handleArchive( rev := "" contentType := "" for extension, mimeType := range map[string]string{ - ".zip": "application/zip", + ".zip": "application/zip", + ".tar.gz": "application/gzip", } { if !strings.HasSuffix(splitPath[2], extension) { continue @@ -562,7 +608,15 @@ func handleArchive( } w.Header().Set("Content-Type", contentType) - z := zip.NewWriter(w) + var z archive + if contentType == "application/gzip" { + gz := gzip.NewWriter(w) + defer gz.Close() + + z = (*tarArchive)(tar.NewWriter(gz)) + } else { + z = (*zipArchive)(zip.NewWriter(w)) + } defer z.Close() err = tree.Walk(func(parent string, entry *git.TreeEntry) error { @@ -576,9 +630,7 @@ func handleArchive( } fullPath := path.Join(parent, entry.Name) if entry.Type == git.ObjectTree { - _, err := z.CreateHeader(&zip.FileHeader{ - Name: fullPath + "/", - }) + _, err := z.Create(fullPath+"/", 0) if err != nil { return errors.Wrap( err, @@ -588,15 +640,6 @@ func handleArchive( return nil } - // Object is a blob. - w, err := z.Create(fullPath) - if err != nil { - return errors.Wrap( - err, - "failed to create zip writer", - ) - } - blob, err := repository.LookupBlob(entry.Id) if err != nil { return errors.Wrapf( @@ -607,6 +650,15 @@ func handleArchive( } defer blob.Free() + // Object is a blob. + w, err := z.Create(fullPath, blob.Size()) + if err != nil { + return errors.Wrap( + err, + "failed to create zip writer", + ) + } + if _, err := w.Write(blob.Contents()); err != nil { return errors.Wrapf( err, diff --git a/browser_test.go b/browser_test.go index c121251..3c87f77 100644 --- a/browser_test.go +++ b/browser_test.go @@ -1,8 +1,10 @@ package githttp import ( + "archive/tar" "archive/zip" "bytes" + "compress/gzip" "context" "net/http/httptest" "reflect" @@ -127,7 +129,7 @@ func TestHandleRestrictedRefs(t *testing.T) { } } -func TestHandleArchiveCommit(t *testing.T) { +func TestHandleArchiveCommitZip(t *testing.T) { log, _ := log15.New("info", false) protocol := NewGitProtocol(GitProtocolOpts{ Log: log, @@ -164,6 +166,51 @@ func TestHandleArchiveCommit(t *testing.T) { } } +func TestHandleArchiveCommitTarball(t *testing.T) { + log, _ := log15.New("info", false) + protocol := NewGitProtocol(GitProtocolOpts{ + Log: log, + }) + + repository, err := git.OpenRepository("testdata/repo.git") + if err != nil { + t.Fatalf("Error opening git repository: %v", err) + } + defer repository.Free() + + response := httptest.NewRecorder() + if err := handleArchive( + context.Background(), + repository, + AuthorizationAllowed, + protocol, + "/+archive/88aa3454adb27c3c343ab57564d962a0a7f6a3c1.tar.gz", + "GET", + response, + ); err != nil { + t.Fatalf("Error getting archive: %v", err) + } + if "application/gzip" != response.Header().Get("Content-Type") { + t.Fatalf("Content-Type. Expected %s, got %s", "application/gzip", response.Header().Get("Content-Type")) + } + + gz, err := gzip.NewReader(bytes.NewReader(response.Body.Bytes())) + if err != nil { + t.Fatalf("Error opening gzip from response: %v", err) + } + defer gz.Close() + + a := tar.NewReader(gz) + hdr, err := a.Next() + if err != nil { + t.Fatalf("Tarball is empty: %v", err) + } + + if "empty" != hdr.Name { + t.Errorf("Expected %s, got %v", "empty", hdr.Name) + } +} + func TestHandleLog(t *testing.T) { log, _ := log15.New("info", false) protocol := NewGitProtocol(GitProtocolOpts{