feat(ui): show link to download directory (#4736)

This adds links to download a directory as archive. This can be useful if you e.g. just want to download a assets directory instead of the full source tree. The logic already exists in the backend, so only the frontend had been changed.

![grafik](https://codeberg.org/attachments/bee69268-ed03-4a05-8505-3d11e977a82c)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4736
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: JakobDev <jakobdev@gmx.de>
Co-committed-by: JakobDev <jakobdev@gmx.de>
This commit is contained in:
JakobDev 2025-02-23 09:23:25 +00:00 committed by 0ko
parent ec35eb2506
commit 3c68399eb0
2 changed files with 62 additions and 0 deletions

View file

@ -144,6 +144,17 @@
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}"> <a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}} {{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
</a> </a>
{{if not $.DisableDownloadSourceArchives}}
<div class="clone-panel ui action tiny input">
<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
{{svg "octicon-kebab-horizontal"}}
<div class="menu">
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments (printf "%s:%s" $.RefName .TreePath)}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments (printf "%s:%s" $.RefName .TreePath)}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
</div>
</button>
</div>
{{end}}
{{end}} {{end}}
</div> </div>
</div> </div>

View file

@ -4,10 +4,17 @@
package integration package integration
import ( import (
"archive/tar"
"compress/gzip"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"testing" "testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
@ -32,3 +39,47 @@ func TestRepoDownloadArchive(t *testing.T) {
assert.Empty(t, resp.Header().Get("Content-Encoding")) assert.Empty(t, resp.Header().Get("Content-Encoding"))
assert.Len(t, bs, 320) assert.Len(t, bs, 320)
} }
func TestRepoDownloadArchiveSubdir(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
defer test.MockVariableValue(&setting.EnableGzip, true)()
defer test.MockVariableValue(&web.GzipMinSize, 10)()
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
// Create a subdirectory
err := createOrReplaceFileInBranch(user, repo, "subdir/test.txt", "master", "Test")
require.NoError(t, err)
t.Run("Frontend", func(t *testing.T) {
resp := MakeRequest(t, NewRequestf(t, "GET", "/%s/src/branch/master/subdir", repo.FullName()), http.StatusOK)
assert.Contains(t, resp.Body.String(), fmt.Sprintf("/%s/archive/master:subdir.zip", repo.FullName()))
assert.Contains(t, resp.Body.String(), fmt.Sprintf("/%s/archive/master:subdir.tar.gz", repo.FullName()))
})
t.Run("Backend", func(t *testing.T) {
resp := MakeRequest(t, NewRequestf(t, "GET", "/%s/archive/master:subdir.tar.gz", repo.FullName()), http.StatusOK)
uncompressedStream, err := gzip.NewReader(resp.Body)
require.NoError(t, err)
tarReader := tar.NewReader(uncompressedStream)
header, err := tarReader.Next()
require.NoError(t, err)
assert.Equal(t, tar.TypeDir, int32(header.Typeflag))
assert.Equal(t, fmt.Sprintf("%s/", repo.Name), header.Name)
header, err = tarReader.Next()
require.NoError(t, err)
assert.Equal(t, tar.TypeReg, int32(header.Typeflag))
assert.Equal(t, fmt.Sprintf("%s/test.txt", repo.Name), header.Name)
_, err = tarReader.Next()
assert.Equal(t, io.EOF, err)
})
})
}