From 9ebdc0993980ceddda103fa2e6dd82f18cd52f77 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 17 Apr 2025 06:34:55 +0000 Subject: [PATCH 001/115] Update module github.com/mattn/go-sqlite3 to v1.14.28 (v11.0/forgejo) (#7563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) | require | patch | `v1.14.24` -> `v1.14.28` | --- ### Release Notes
mattn/go-sqlite3 (github.com/mattn/go-sqlite3) ### [`v1.14.28`](https://github.com/mattn/go-sqlite3/compare/v1.14.27...v1.14.28) [Compare Source](https://github.com/mattn/go-sqlite3/compare/v1.14.27...v1.14.28) ### [`v1.14.27`](https://github.com/mattn/go-sqlite3/compare/v1.14.26...v1.14.27) [Compare Source](https://github.com/mattn/go-sqlite3/compare/v1.14.26...v1.14.27) ### [`v1.14.26`](https://github.com/mattn/go-sqlite3/compare/v1.14.25...v1.14.26) [Compare Source](https://github.com/mattn/go-sqlite3/compare/v1.14.25...v1.14.26) ### [`v1.14.25`](https://github.com/mattn/go-sqlite3/compare/v1.14.24...v1.14.25) [Compare Source](https://github.com/mattn/go-sqlite3/compare/v1.14.24...v1.14.25)
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - "* 0-3 * * *" (UTC). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7563 Reviewed-by: Earl Warren Co-authored-by: Renovate Bot Co-committed-by: Renovate Bot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b4ca6c444d..61232b7b15 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/lib/pq v1.10.9 github.com/markbates/goth v1.80.0 github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-sqlite3 v1.14.24 + github.com/mattn/go-sqlite3 v1.14.28 github.com/meilisearch/meilisearch-go v0.31.0 github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.27 diff --git a/go.sum b/go.sum index 3e0bcb61b2..16276ffb3a 100644 --- a/go.sum +++ b/go.sum @@ -1252,8 +1252,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY= github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g= github.com/mholt/acmez/v3 v3.1.1 h1:Jh+9uKHkPxUJdxM16q5mOr+G2V0aqkuFtNA28ihCxhQ= From 722ea4179c59166c708f8a614644c66917ad6af7 Mon Sep 17 00:00:00 2001 From: Beowulf Date: Sat, 19 Apr 2025 07:03:09 +0000 Subject: [PATCH 002/115] [v11.0/forgejo] fix(ui): show commit icon in branch dropdown button when viewing a commit (#7576) Backport: https://codeberg.org/forgejo/forgejo/pulls/7571 Replaces https://codeberg.org/forgejo/forgejo/pulls/7574 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7576 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Beowulf Co-committed-by: Beowulf --- templates/repo/branch_dropdown.tmpl | 4 ++- tests/e2e/declare_repos_test.go | 8 +++++ tests/e2e/repo-home.e2e.ts | 19 ---------- tests/e2e/repo-home.test.e2e.ts | 35 +++++++++++++++++++ .../js/components/RepoBranchTagSelector.vue | 11 +++--- 5 files changed, 52 insertions(+), 25 deletions(-) delete mode 100644 tests/e2e/repo-home.e2e.ts create mode 100644 tests/e2e/repo-home.test.e2e.ts diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl index dcb1792485..ac4f8d2b75 100644 --- a/templates/repo/branch_dropdown.tmpl +++ b/templates/repo/branch_dropdown.tmpl @@ -77,8 +77,10 @@ {{else}} {{if eq $type "tag"}} {{svg "octicon-tag"}} - {{else}} + {{else if eq $type "branch"}} {{svg "octicon-git-branch"}} + {{else}} + {{svg "octicon-git-commit"}} {{end}} {{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}} {{end}} diff --git a/tests/e2e/declare_repos_test.go b/tests/e2e/declare_repos_test.go index f701b11f97..f45687651c 100644 --- a/tests/e2e/declare_repos_test.go +++ b/tests/e2e/declare_repos_test.go @@ -13,6 +13,7 @@ import ( "forgejo.org/models/unittest" user_model "forgejo.org/models/user" "forgejo.org/modules/git" + "forgejo.org/modules/indexer/stats" files_service "forgejo.org/services/repository/files" "forgejo.org/tests" @@ -36,6 +37,10 @@ func DeclareGitRepos(t *testing.T) func() { Filename: "testfile", Versions: []string{"hello", "hallo", "hola", "native", "ubuntu-latest", "- runs-on: ubuntu-latest", "- runs-on: debian-latest"}, }}), + newRepo(t, 2, "language-stats-test", []FileChanges{{ + Filename: "main.rs", + Versions: []string{"fn main() {", "println!(\"Hello World!\");", "}"}, + }}), newRepo(t, 2, "mentions-highlighted", []FileChanges{ { Filename: "history1.md", @@ -105,5 +110,8 @@ func newRepo(t *testing.T, userID int64, repoName string, fileChanges []FileChan } } + err := stats.UpdateRepoIndexer(somerepo) + require.NoError(t, err) + return cleanupFunc } diff --git a/tests/e2e/repo-home.e2e.ts b/tests/e2e/repo-home.e2e.ts deleted file mode 100644 index fbcfe17226..0000000000 --- a/tests/e2e/repo-home.e2e.ts +++ /dev/null @@ -1,19 +0,0 @@ -// @watch start -// web_src/js/features/common-global.js -// web_src/css/repo.css -// @watch end - -import {expect} from '@playwright/test'; -import {save_visual, test} from './utils_e2e.ts'; - -test('Language stats bar', async ({page}) => { - const response = await page.goto('/user2/repo1'); - expect(response?.status()).toBe(200); - - await expect(page.locator('#language-stats-legend')).toBeVisible(); - await save_visual(page); - - await page.click('#language-stats-bar'); - await expect(page.locator('#language-stats-legend')).toBeHidden(); - await save_visual(page); -}); diff --git a/tests/e2e/repo-home.test.e2e.ts b/tests/e2e/repo-home.test.e2e.ts new file mode 100644 index 0000000000..6f3d6c373b --- /dev/null +++ b/tests/e2e/repo-home.test.e2e.ts @@ -0,0 +1,35 @@ +// @watch start +// web_src/js/components/RepoBranchTagSelector.vue +// web_src/js/features/common-global.js +// web_src/css/repo.css +// @watch end + +import {expect} from '@playwright/test'; +import {save_visual, test} from './utils_e2e.ts'; + +test('Language stats bar', async ({page}) => { + const response = await page.goto('/user2/language-stats-test'); + expect(response?.status()).toBe(200); + + await expect(page.locator('#language-stats-legend')).toBeHidden(); + + await page.click('#language-stats-bar'); + await expect(page.locator('#language-stats-legend')).toBeVisible(); + await save_visual(page); + + await page.click('#language-stats-bar'); + await expect(page.locator('#language-stats-legend')).toBeHidden(); + await save_visual(page); +}); + +test('Branch selector commit icon', async ({page}) => { + const response = await page.goto('/user2/repo1'); + expect(response?.status()).toBe(200); + + await expect(page.locator('.branch-dropdown-button svg.octicon-git-branch')).toBeVisible(); + await expect(page.locator('.branch-dropdown-button')).toHaveText('master'); + + await page.goto('/user2/repo1/src/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d'); + await expect(page.locator('.branch-dropdown-button svg.octicon-git-commit')).toBeVisible(); + await expect(page.locator('.branch-dropdown-button')).toHaveText('65f1bf27bc'); +}); diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue index cd86499ddc..881c7c09ea 100644 --- a/web_src/js/components/RepoBranchTagSelector.vue +++ b/web_src/js/components/RepoBranchTagSelector.vue @@ -54,12 +54,12 @@ const sfc = { if (this.viewType === 'tree') { this.isViewTree = true; this.refNameText = this.commitIdShort; - } else if (this.viewType === 'tag') { - this.isViewTag = true; - this.refNameText = this.tagName; - } else { + } else if (this.viewType === 'branch') { this.isViewBranch = true; this.refNameText = this.branchName; + } else { + this.isViewTag = true; + this.refNameText = this.tagName; } document.body.addEventListener('click', (event) => { @@ -252,7 +252,8 @@ export default sfc; // activate IDE's Vue plugin From 973bc33a5f35e6080b5daa5be0b6b48d64abe4ad Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Sun, 20 Apr 2025 21:18:49 +0000 Subject: [PATCH 003/115] [v11.0/forgejo] fix(ui/pr): use eye icon for reviews (#7585) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7584 Followup to #6523 Reverts icon change introduced in #5695 It went unnoticed since v10 and was likely caused by bad copy paste and I don't think it's really worth a test. Co-authored-by: 0ko <0ko@noreply.codeberg.org> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7585 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- templates/repo/issue/view_content/comments.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index bea517dfa2..f7d4f7c96e 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -526,7 +526,7 @@ {{else if eq .Type 27}} {{if or .AddedRequestReview .RemovedRequestReview}}
- {{svg "octicon-tag"}} + {{svg "octicon-eye"}} {{template "shared/user/avatarlink" dict "user" .Poster}} {{if and (eq (len .RemovedRequestReview) 1) (eq (len .AddedRequestReview) 0) (eq ((index .RemovedRequestReview 0).ID) .PosterID) (eq ((index .RemovedRequestReview 0).Type) "user")}} @@ -724,7 +724,7 @@ {{if or .AddedRequestReview .RemovedRequestReview}}
  • - {{svg "octicon-tag" 20}} + {{svg "octicon-eye" 20}} {{if and (eq (len .RemovedRequestReview) 1) (eq (len .AddedRequestReview) 0) (eq ((index .RemovedRequestReview 0).ID) .PosterID) (eq ((index .RemovedRequestReview 0).Type) "user")}} From 5395eea33870b6e860a5f4003c9545f7e7975a89 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Mon, 21 Apr 2025 07:20:28 +0000 Subject: [PATCH 004/115] [v11.0/forgejo] fix(ui): rescope menu height patch to overflow menu (#7583) Followup to https://codeberg.org/forgejo/forgejo/pulls/7108 v11-specific backport of https://codeberg.org/forgejo/forgejo/pulls/7578, see details here (change 2) Preview: https://codeberg.org/attachments/661c50be-7673-4970-a78b-5852b1138417) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7583 Reviewed-by: Gusted --- web_src/css/base.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web_src/css/base.css b/web_src/css/base.css index 5b0085ee0d..cca83fc7e2 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1050,6 +1050,7 @@ overflow-menu .overflow-menu-items { overflow-menu .overflow-menu-items .item { margin-bottom: 0 !important; /* reset fomantic's margin, because the active menu has special bottom border */ + height: 100%; } overflow-menu .ui.label { @@ -1356,10 +1357,6 @@ table th[data-sortt-desc] .svg { border-color: var(--color-secondary); } -.ui.tabular.menu .item { - height: 100%; -} - .ui.tabular.menu .item, .ui.secondary.pointing.menu .item { padding: 11px 12px !important; From 9147665e2c51013c56b2214a5615cb060a26aeae Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Mon, 21 Apr 2025 09:42:23 +0000 Subject: [PATCH 005/115] [v11.0/forgejo] fix(ui): use gap in switch items (#7589) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7581 Followup to _many PRs_ where the old switches were converted to this one. The main change here is to the switch with counter. It was missing a gap. Additionally, it removes tailwind helpers the other switches retained from before switch refactors, because they are now using gap. Co-authored-by: 0ko <0ko@noreply.codeberg.org> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7589 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- templates/projects/list.tmpl | 4 ++-- templates/repo/graph.tmpl | 4 ++-- templates/repo/issue/openclose.tmpl | 10 +++++----- templates/user/dashboard/issues.tmpl | 4 ++-- templates/user/dashboard/milestones.tmpl | 4 ++-- web_src/css/modules/switch.css | 1 + 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 5a9ba004eb..8c9b7c6e6f 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -2,11 +2,11 @@
    diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl index 25f73a857d..5d5cf4140e 100644 --- a/templates/repo/issue/openclose.tmpl +++ b/templates/repo/issue/openclose.tmpl @@ -1,21 +1,21 @@
    {{if .PageIsMilestones}} - {{svg "octicon-milestone" 16 "tw-mr-2"}} + {{svg "octicon-milestone" 16}} {{else if .PageIsPullList}} - {{svg "octicon-git-pull-request" 16 "tw-mr-2"}} + {{svg "octicon-git-pull-request" 16}} {{else}} - {{svg "octicon-issue-opened" 16 "tw-mr-2"}} + {{svg "octicon-issue-opened" 16}} {{end}} {{ctx.Locale.PrettyNumber .OpenCount}} {{ctx.Locale.Tr "repo.issues.open_title"}} - {{svg "octicon-issue-closed" 16 "tw-mr-2"}} + {{svg "octicon-issue-closed" 16}} {{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}} {{if not .PageIsMilestones}} - {{svg "octicon-eye" 16 "tw-mr-2"}} + {{svg "octicon-eye" 16}} {{ctx.Locale.PrettyNumber (.AllCount)}} {{ctx.Locale.Tr "repo.issues.all_title"}} {{end}} diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index e25a9d8219..c76a61c393 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -6,11 +6,11 @@
    diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index b269c63b37..347e7a81bf 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -37,11 +37,11 @@
    diff --git a/web_src/css/modules/switch.css b/web_src/css/modules/switch.css index a9499a84aa..7780155787 100644 --- a/web_src/css/modules/switch.css +++ b/web_src/css/modules/switch.css @@ -11,6 +11,7 @@ .switch .item { display: flex; + gap: 0.5rem; align-items: center; padding: .5em 1.125em; color: var(--color-text); From 8d3dd3aa02d3e31e544ae03014e9339159eb1a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 22 Nov 2024 16:38:23 +0100 Subject: [PATCH 006/115] Run testing workflow unconditionally --- .forgejo/workflows/testing.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 62136b1b28..9ba8c47a01 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -10,7 +10,6 @@ on: jobs: backend-checks: - if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker container: image: 'data.forgejo.org/oci/node:22-bookworm' @@ -27,7 +26,6 @@ jobs: - run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check lint-swagger fmt-check swagger-validate' # ensure the "go-licenses" make target runs - uses: ./.forgejo/workflows-composite/build-backend frontend-checks: - if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker container: image: 'data.forgejo.org/oci/node:22-bookworm' @@ -176,7 +174,6 @@ jobs: TAGS: bindata TEST_REDIS_SERVER: cacher:${{ matrix.cacher.port }} test-mysql: - if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: @@ -207,7 +204,6 @@ jobs: env: USE_REPO_TEST_DIR: 1 test-pgsql: - if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: @@ -246,7 +242,6 @@ jobs: USE_REPO_TEST_DIR: 1 TEST_LDAP: 1 test-sqlite: - if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: [backend-checks, frontend-checks] container: @@ -269,14 +264,11 @@ jobs: TEST_TAGS: sqlite sqlite_unlock_notify USE_REPO_TEST_DIR: 1 security-check: - if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker needs: - test-sqlite - test-pgsql - test-mysql - - test-remote-cacher - - test-unit container: image: 'data.forgejo.org/oci/node:22-bookworm' options: --tmpfs /tmp:exec,noatime From 5ffe4e54e1b1501357da433797c329e59d61d423 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 23 Apr 2025 13:40:51 +0000 Subject: [PATCH 007/115] [v11.0/forgejo] fix: delay-write trace.dat for forgejo diagnosis (#7601) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7597 - Delay the writing of `trace.dat` to the forgejo diagnosis zip file. It's not possible to write `cpu-profile.dat` and `trace.dat` at the same time. This caused the implemention to prematurely close `cpu-profile.dat` and leave it as an empty file. - Regression of forgejo/forgejo#6470 ## Testing 1. Go to `/admin/monitor` 2. Click on "Download diagnosis report" 3. Open the zip file and check that all files are non-empty. Co-authored-by: Gusted Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7601 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- routers/web/admin/diagnosis.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/routers/web/admin/diagnosis.go b/routers/web/admin/diagnosis.go index e436dca663..8b0ec45214 100644 --- a/routers/web/admin/diagnosis.go +++ b/routers/web/admin/diagnosis.go @@ -5,7 +5,9 @@ package admin import ( "archive/zip" + "bytes" "fmt" + "io" "runtime" "runtime/pprof" "runtime/trace" @@ -45,14 +47,9 @@ func MonitorDiagnosis(ctx *context.Context) { _, _ = f.Write([]byte(err.Error())) } - f, err = zipWriter.CreateHeader(&zip.FileHeader{Name: "trace.dat", Method: zip.Deflate, Modified: time.Now()}) - if err != nil { - ctx.ServerError("Failed to create zip file", err) - return - } - - if err := trace.Start(f); err != nil { - _, _ = f.Write([]byte(err.Error())) + traceBuf := &bytes.Buffer{} + if err := trace.Start(traceBuf); err != nil { + _, _ = traceBuf.Write([]byte(err.Error())) } select { @@ -62,6 +59,17 @@ func MonitorDiagnosis(ctx *context.Context) { pprof.StopCPUProfile() trace.Stop() + f, err = zipWriter.CreateHeader(&zip.FileHeader{Name: "trace.dat", Method: zip.Deflate, Modified: time.Now()}) + if err != nil { + ctx.ServerError("Failed to create zip file", err) + return + } + + if _, err := io.Copy(f, traceBuf); err != nil { + ctx.ServerError("Failed to create zip file", err) + return + } + f, err = zipWriter.CreateHeader(&zip.FileHeader{Name: "goroutine-after.txt", Method: zip.Deflate, Modified: time.Now()}) if err != nil { ctx.ServerError("Failed to create zip file", err) From 55cff9cfb415f99c2900a03738887af84efaee6a Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 23 Apr 2025 13:44:16 +0000 Subject: [PATCH 008/115] [v11.0/forgejo] fix(i18n): prevent incorrect logging on strings missing in JSON locales (#7599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7594 Fixes https://codeberg.org/forgejo/forgejo/issues/7591 Followup to https://codeberg.org/forgejo/forgejo/pulls/6203 Without the 3rd commit, when this test runs, an error message appears in the logs: ``` [E] Missing translation "incorrect_root_url" ``` With it, it does not. No changes to the actual locale handling are expected and I'm hoping there's enough other testing in place for that. Reported-by: Paweł Bogusławski Co-authored-by: 0ko <0ko@noreply.codeberg.org> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7599 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- modules/translation/i18n/localestore.go | 2 ++ ...nslations_test.go => translations_test.go} | 33 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) rename tests/integration/{size_translations_test.go => translations_test.go} (77%) diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go index 823cadf8f9..1b64139690 100644 --- a/modules/translation/i18n/localestore.go +++ b/modules/translation/i18n/localestore.go @@ -1,4 +1,5 @@ // Copyright 2022 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package i18n @@ -239,6 +240,7 @@ func (l *locale) TrString(trKey string, trArgs ...any) string { if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok { if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" { format = msg + found = true } else if foundIndex { // Third fallback: old-style default language if msg, ok := defaultLang.idxToMsgMap[idx]; ok { diff --git a/tests/integration/size_translations_test.go b/tests/integration/translations_test.go similarity index 77% rename from tests/integration/size_translations_test.go rename to tests/integration/translations_test.go index d34cd0b490..9cfa3423b7 100644 --- a/tests/integration/size_translations_test.go +++ b/tests/integration/translations_test.go @@ -1,6 +1,5 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - +// SPDX-License-Identifier: GPL-3.0-or-later package integration import ( @@ -13,6 +12,7 @@ import ( "forgejo.org/models/unittest" user_model "forgejo.org/models/user" + "forgejo.org/modules/translation/i18n" files_service "forgejo.org/services/repository/files" "forgejo.org/tests" @@ -20,6 +20,29 @@ import ( "github.com/stretchr/testify/assert" ) +func TestMissingTranslationHandling(t *testing.T) { + // Currently new languages can only be added to localestore via AddLocaleByIni + // so this line is here to make the other one work. When INI locales are removed, + // it will not be needed by this test. + i18n.DefaultLocales.AddLocaleByIni("fun", "Funlang", nil, []byte(""), nil) + + // Add a testing locale to the store + i18n.DefaultLocales.AddToLocaleFromJSON("fun", []byte(`{ + "meta.last_line": "This language only has one line that is never used by the UI. It will never have a translation for incorrect_root_url" + }`)) + + // Get "fun" locale, make sure it's available + funLocale, found := i18n.DefaultLocales.Locale("fun") + assert.True(t, found) + + // Get translation for a string that this locale doesn't have + s := funLocale.TrString("incorrect_root_url") + + // Verify fallback to English + assert.True(t, strings.HasPrefix(s, "This Forgejo instance")) +} + +// TestDataSizeTranslation is a test for usage of TrSize in file size display func TestDataSizeTranslation(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { testUser := "user2" @@ -103,14 +126,14 @@ func testFileSizeTranslated(t *testing.T, session *TestSession, filePath, correc resp := session.MakeRequest(t, req, http.StatusOK) // Check if file size is translated - sizeCorrent := false + sizeCorrect := false fileInfo := NewHTMLParser(t, resp.Body).Find(".file-info .file-info-entry") fileInfo.Each(func(i int, info *goquery.Selection) { infoText := strings.TrimSpace(info.Text()) if infoText == correctSize { - sizeCorrent = true + sizeCorrect = true } }) - assert.True(t, sizeCorrent) + assert.True(t, sizeCorrect) } From a4396782b55be9db1651190fcddbcea0dc9b5069 Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 23 Apr 2025 23:39:27 +0200 Subject: [PATCH 009/115] fix(ui): make pagination labels always visible to screenreader - The pagination labels 'First', 'Previous', 'Next' and 'Last' are hidden away when the screen width becomes smaller. However this also hides them from the screen reader. Instead of using `display: none`, use some well-known tricks to still make them visible to the screen reader. - Add E2E test. - Resolves Codeberg/Community#1858 (cherry picked from commit b6072496d4b63314a7203d302b3c2e995f83d143) --- tests/e2e/pagination.test.e2e.ts | 15 +++++++++++++++ web_src/css/base.css | 10 +++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/pagination.test.e2e.ts diff --git a/tests/e2e/pagination.test.e2e.ts b/tests/e2e/pagination.test.e2e.ts new file mode 100644 index 0000000000..3126b669a6 --- /dev/null +++ b/tests/e2e/pagination.test.e2e.ts @@ -0,0 +1,15 @@ +// @watch start +// template/base/paginate.tmpl +// services/context/pagination.go +// @watch end + +import {expect} from '@playwright/test'; +import {test} from './utils_e2e.ts'; +import {accessibilityCheck} from './shared/accessibility.ts'; + +test('Pagination a11y', async ({page}) => { + await page.goto('/explore/repos'); + + await expect(page.locator('.pagination')).toBeVisible(); + await accessibilityCheck({page}, ['.pagination'], [], []); +}); diff --git a/web_src/css/base.css b/web_src/css/base.css index cca83fc7e2..ad6e8a2a24 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -948,7 +948,15 @@ img.ui.avatar, @media (max-width: 767.98px) { .ui.pagination.menu .item:not(.active,.navigation), .ui.pagination.menu .item.navigation span.navigation_label { - display: none; + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } } From c5bfe7787330955c74c147609e8eab073fda17fe Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Fri, 25 Apr 2025 09:59:30 +0000 Subject: [PATCH 010/115] [v11.0/forgejo] chore: merge tests.AddFixtures and unittest.OverrideFixtures (#7649) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7648 The only parameter that is ever used is a single directory, make it that only instead of a more complex option structure. Remove tests.AddFixtures that was the simpler form because it is now redundant. --- Backporting to v11.0 will help with automated backporting of bug fixes in need of custom made fixtures. Co-authored-by: Earl Warren Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7649 Reviewed-by: Earl Warren Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- models/auth/oauth2_test.go | 10 +--------- models/git/lfs_test.go | 9 +-------- models/issues/issue_project_test.go | 4 ++-- models/issues/pull_test.go | 3 +-- models/issues/stopwatch_test.go | 10 +--------- models/organization/team_test.go | 10 +--------- models/packages/main_test.go | 12 ------------ models/packages/package_blob_test.go | 2 +- models/repo/repo_list_test.go | 10 +--------- models/unittest/fixtures.go | 9 +++++++-- models/user/main_test.go | 1 + models/user/user_test.go | 7 +++---- services/user/user_test.go | 8 +------- services/webhook/notifier_test.go | 17 ++--------------- tests/integration/actions_variables_test.go | 2 +- tests/integration/admin_user_test.go | 2 +- tests/integration/block_test.go | 4 ++-- tests/integration/comment_roles_system_test.go | 2 +- tests/integration/feed_user_test.go | 3 ++- tests/integration/issue_test.go | 4 ++-- tests/integration/private_project_test.go | 2 +- tests/integration/pull_review_test.go | 2 +- tests/integration/pull_test.go | 2 +- tests/integration/runner_test.go | 2 +- tests/integration/xss_test.go | 2 +- tests/test_utils.go | 10 ---------- 26 files changed, 37 insertions(+), 112 deletions(-) diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index 877541e0ff..65865c6d31 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -4,14 +4,12 @@ package auth_test import ( - "path/filepath" "slices" "testing" auth_model "forgejo.org/models/auth" "forgejo.org/models/db" "forgejo.org/models/unittest" - "forgejo.org/modules/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -275,13 +273,7 @@ func TestBuiltinApplicationsClientIDs(t *testing.T) { } func TestOrphanedOAuth2Applications(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"models/auth/TestOrphanedOAuth2Applications/"}, - }, - )() + defer unittest.OverrideFixtures("models/auth/TestOrphanedOAuth2Applications")() require.NoError(t, unittest.PrepareTestDatabase()) count, err := auth_model.CountOrphanedOAuth2Applications(db.DefaultContext) diff --git a/models/git/lfs_test.go b/models/git/lfs_test.go index 8a2d3c8caf..af5e1abd90 100644 --- a/models/git/lfs_test.go +++ b/models/git/lfs_test.go @@ -5,7 +5,6 @@ package git import ( "context" - "path/filepath" "testing" "forgejo.org/models/db" @@ -18,13 +17,7 @@ import ( ) func TestIterateRepositoryIDsWithLFSMetaObjects(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"models/git/TestIterateRepositoryIDsWithLFSMetaObjects/"}, - }, - )() + defer unittest.OverrideFixtures("models/git/TestIterateRepositoryIDsWithLFSMetaObjects")() require.NoError(t, unittest.PrepareTestDatabase()) type repocount struct { diff --git a/models/issues/issue_project_test.go b/models/issues/issue_project_test.go index f518679f34..099679a8c7 100644 --- a/models/issues/issue_project_test.go +++ b/models/issues/issue_project_test.go @@ -20,7 +20,7 @@ import ( ) func TestPrivateIssueProjects(t *testing.T) { - defer tests.AddFixtures("models/fixtures/PrivateIssueProjects/")() + defer unittest.OverrideFixtures("models/fixtures/PrivateIssueProjects")() require.NoError(t, unittest.PrepareTestDatabase()) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -119,7 +119,7 @@ func TestPrivateIssueProjects(t *testing.T) { } func TestPrivateRepoProjects(t *testing.T) { - defer tests.AddFixtures("models/fixtures/TestPrivateRepoProjects/")() + defer unittest.OverrideFixtures("models/fixtures/TestPrivateRepoProjects")() require.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index a2dde055a6..e85b626c83 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -14,7 +14,6 @@ import ( "forgejo.org/models/unittest" user_model "forgejo.org/models/user" "forgejo.org/modules/setting" - "forgejo.org/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -161,7 +160,7 @@ func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) { } func TestGetUnmergedPullRequestsByHeadInfoMax(t *testing.T) { - defer tests.AddFixtures("models/fixtures/TestGetUnmergedPullRequestsByHeadInfoMax/")() + defer unittest.OverrideFixtures("models/fixtures/TestGetUnmergedPullRequestsByHeadInfoMax")() require.NoError(t, unittest.PrepareTestDatabase()) repoID := int64(1) diff --git a/models/issues/stopwatch_test.go b/models/issues/stopwatch_test.go index 8920023a3b..3334ffea7d 100644 --- a/models/issues/stopwatch_test.go +++ b/models/issues/stopwatch_test.go @@ -4,14 +4,12 @@ package issues_test import ( - "path/filepath" "testing" "forgejo.org/models/db" issues_model "forgejo.org/models/issues" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" - "forgejo.org/modules/setting" "forgejo.org/modules/timeutil" "github.com/stretchr/testify/assert" @@ -81,13 +79,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) { } func TestGetUIDsAndStopwatch(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"models/issues/TestGetUIDsAndStopwatch/"}, - }, - )() + defer unittest.OverrideFixtures("models/issues/TestGetUIDsAndStopwatch")() require.NoError(t, unittest.PrepareTestDatabase()) uidStopwatches, err := issues_model.GetUIDsAndStopwatch(db.DefaultContext) diff --git a/models/organization/team_test.go b/models/organization/team_test.go index 0237f76de8..1be96b6a01 100644 --- a/models/organization/team_test.go +++ b/models/organization/team_test.go @@ -4,14 +4,12 @@ package organization_test import ( - "path/filepath" "testing" "forgejo.org/models/db" "forgejo.org/models/organization" "forgejo.org/models/perm" "forgejo.org/models/unittest" - "forgejo.org/modules/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -189,13 +187,7 @@ func TestHasTeamRepo(t *testing.T) { } func TestInconsistentOwnerTeam(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"models/organization/TestInconsistentOwnerTeam/"}, - }, - )() + defer unittest.OverrideFixtures("models/organization/TestInconsistentOwnerTeam")() require.NoError(t, unittest.PrepareTestDatabase()) unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ID: 1000, TeamID: 1000, AccessMode: perm.AccessModeNone}) diff --git a/models/packages/main_test.go b/models/packages/main_test.go index 8114461502..f9083d705d 100644 --- a/models/packages/main_test.go +++ b/models/packages/main_test.go @@ -4,11 +4,9 @@ package packages import ( - "path/filepath" "testing" "forgejo.org/models/unittest" - "forgejo.org/modules/setting" _ "forgejo.org/models" _ "forgejo.org/models/actions" @@ -16,16 +14,6 @@ import ( _ "forgejo.org/models/forgefed" ) -func AddFixtures(dirs ...string) func() { - return unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: dirs, - }, - ) -} - func TestMain(m *testing.M) { unittest.MainTest(m) } diff --git a/models/packages/package_blob_test.go b/models/packages/package_blob_test.go index 7de6aab15a..664dfa4d81 100644 --- a/models/packages/package_blob_test.go +++ b/models/packages/package_blob_test.go @@ -13,7 +13,7 @@ import ( ) func TestPackagesGetOrInsertBlob(t *testing.T) { - defer AddFixtures("models/fixtures/TestPackagesGetOrInsertBlob/")() + defer unittest.OverrideFixtures("models/fixtures/TestPackagesGetOrInsertBlob")() require.NoError(t, unittest.PrepareTestDatabase()) blake2bIsSet := unittest.AssertExistsAndLoadBean(t, &PackageBlob{ID: 1}) diff --git a/models/repo/repo_list_test.go b/models/repo/repo_list_test.go index b13db64137..c654d1b602 100644 --- a/models/repo/repo_list_test.go +++ b/models/repo/repo_list_test.go @@ -4,7 +4,6 @@ package repo_test import ( - "path/filepath" "slices" "strings" "testing" @@ -14,7 +13,6 @@ import ( "forgejo.org/models/unittest" "forgejo.org/models/user" "forgejo.org/modules/optional" - "forgejo.org/modules/setting" "forgejo.org/modules/structs" "github.com/stretchr/testify/assert" @@ -410,13 +408,7 @@ func TestSearchRepositoryByTopicName(t *testing.T) { } func TestSearchRepositoryIDsByCondition(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"models/repo/TestSearchRepositoryIDsByCondition/"}, - }, - )() + defer unittest.OverrideFixtures("models/repo/TestSearchRepositoryIDsByCondition")() require.NoError(t, unittest.PrepareTestDatabase()) // Sanity check of the database limitedUser := unittest.AssertExistsAndLoadBean(t, &user.User{ID: 33, Visibility: structs.VisibleTypeLimited}) diff --git a/models/unittest/fixtures.go b/models/unittest/fixtures.go index 6402fd9466..329071ce28 100644 --- a/models/unittest/fixtures.go +++ b/models/unittest/fixtures.go @@ -29,9 +29,14 @@ func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) { return db.DefaultContext.(*db.Context).Engine().(*xorm.Engine) } -func OverrideFixtures(opts FixturesOptions, engine ...*xorm.Engine) func() { +func OverrideFixtures(dir string) func() { old := fixturesLoader - if err := InitFixtures(opts, engine...); err != nil { + opts := FixturesOptions{ + Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), + Base: setting.AppWorkPath, + Dirs: []string{dir}, + } + if err := InitFixtures(opts); err != nil { panic(err) } return func() { diff --git a/models/user/main_test.go b/models/user/main_test.go index 685b1116f3..f0dae086e0 100644 --- a/models/user/main_test.go +++ b/models/user/main_test.go @@ -11,6 +11,7 @@ import ( _ "forgejo.org/models" _ "forgejo.org/models/actions" _ "forgejo.org/models/activities" + _ "forgejo.org/models/forgefed" _ "forgejo.org/models/user" ) diff --git a/models/user/user_test.go b/models/user/user_test.go index dde0146470..63a12b788f 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -25,7 +25,6 @@ import ( "forgejo.org/modules/timeutil" "forgejo.org/modules/util" "forgejo.org/modules/validation" - "forgejo.org/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -73,7 +72,7 @@ func TestGetUserFromMap(t *testing.T) { } func TestGetUserByName(t *testing.T) { - defer tests.AddFixtures("models/user/fixtures/")() + defer unittest.OverrideFixtures("models/user/fixtures")() require.NoError(t, unittest.PrepareTestDatabase()) { @@ -120,7 +119,7 @@ func TestCanCreateOrganization(t *testing.T) { } func TestGetAllUsers(t *testing.T) { - defer tests.AddFixtures("models/user/fixtures/")() + defer unittest.OverrideFixtures("models/user/fixtures")() require.NoError(t, unittest.PrepareTestDatabase()) users, err := user_model.GetAllUsers(db.DefaultContext) @@ -145,7 +144,7 @@ func TestAPActorID(t *testing.T) { } func TestSearchUsers(t *testing.T) { - defer tests.AddFixtures("models/user/fixtures/")() + defer unittest.OverrideFixtures("models/user/fixtures")() require.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) { users, _, err := user_model.SearchUsers(db.DefaultContext, opts) diff --git a/services/user/user_test.go b/services/user/user_test.go index ebcdb17710..64a9ed2d23 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -67,13 +67,7 @@ func TestDeleteUser(t *testing.T) { } func TestPurgeUser(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"services/user/TestPurgeUser/"}, - }, - )() + defer unittest.OverrideFixtures("services/user/TestPurgeUser")() require.NoError(t, unittest.PrepareTestDatabase()) defer test.MockVariableValue(&setting.SSH.RootPath, t.TempDir())() defer test.MockVariableValue(&setting.SSH.CreateAuthorizedKeysFile, true)() diff --git a/services/webhook/notifier_test.go b/services/webhook/notifier_test.go index 6b1f170287..46eb1f089c 100644 --- a/services/webhook/notifier_test.go +++ b/services/webhook/notifier_test.go @@ -4,7 +4,6 @@ package webhook import ( - "path/filepath" "testing" "forgejo.org/models/db" @@ -56,13 +55,7 @@ func pushCommits() *repository.PushCommits { } func TestSyncPushCommits(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"services/webhook/TestPushCommits"}, - }, - )() + defer unittest.OverrideFixtures("services/webhook/TestPushCommits")() require.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -95,13 +88,7 @@ func TestSyncPushCommits(t *testing.T) { } func TestPushCommits(t *testing.T) { - defer unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: []string{"services/webhook/TestPushCommits"}, - }, - )() + defer unittest.OverrideFixtures("services/webhook/TestPushCommits")() require.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/actions_variables_test.go b/tests/integration/actions_variables_test.go index 0b59284f27..b27d30a480 100644 --- a/tests/integration/actions_variables_test.go +++ b/tests/integration/actions_variables_test.go @@ -19,7 +19,7 @@ import ( ) func TestActionVariablesModification(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestActionVariablesModification")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestActionVariablesModification")() defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go index f98650c567..b7a9deb522 100644 --- a/tests/integration/admin_user_test.go +++ b/tests/integration/admin_user_test.go @@ -98,7 +98,7 @@ func makeRequest(t *testing.T, formData user_model.User, headerCode int) { } func TestAdminDeleteUser(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestAdminDeleteUser/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestAdminDeleteUser")() defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") diff --git a/tests/integration/block_test.go b/tests/integration/block_test.go index d249737555..7a2756522b 100644 --- a/tests/integration/block_test.go +++ b/tests/integration/block_test.go @@ -167,7 +167,7 @@ func TestBlockUserFromOrganization(t *testing.T) { // and as a blocked user and are handled cleanly after the blocking has taken // place. func TestBlockActions(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestBlockActions/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestBlockActions")() defer tests.PrepareTestEnv(t)() doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -438,7 +438,7 @@ func TestBlockActions(t *testing.T) { } func TestBlockedNotification(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestBlockedNotifications")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestBlockedNotifications")() defer tests.PrepareTestEnv(t)() doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/comment_roles_system_test.go b/tests/integration/comment_roles_system_test.go index 3b315f28f8..0b4e78048a 100644 --- a/tests/integration/comment_roles_system_test.go +++ b/tests/integration/comment_roles_system_test.go @@ -19,7 +19,7 @@ import ( // As it is not possible to do actions as system users, the tests are done using fixtures. func TestSystemCommentRoles(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestSystemCommentRoles/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestSystemCommentRoles")() defer tests.PrepareTestEnv(t)() repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) diff --git a/tests/integration/feed_user_test.go b/tests/integration/feed_user_test.go index 51da24252a..dfcb33b35d 100644 --- a/tests/integration/feed_user_test.go +++ b/tests/integration/feed_user_test.go @@ -9,6 +9,7 @@ import ( "testing" "forgejo.org/models/db" + "forgejo.org/models/unittest" user_model "forgejo.org/models/user" "forgejo.org/tests" @@ -33,7 +34,7 @@ type RSS struct { } func TestFeed(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestFeed/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestFeed")() defer tests.PrepareTestEnv(t)() t.Run("User", func(t *testing.T) { diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 5b06b15dd7..6845612a6d 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -1084,7 +1084,7 @@ func TestIssueReferenceURL(t *testing.T) { } func TestGetContentHistory(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestGetContentHistory/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestGetContentHistory")() defer tests.PrepareTestEnv(t)() issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) @@ -1136,7 +1136,7 @@ func TestGetContentHistory(t *testing.T) { } func TestCommitRefComment(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestCommitRefComment/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestCommitRefComment")() defer tests.PrepareTestEnv(t)() t.Run("Pull request", func(t *testing.T) { diff --git a/tests/integration/private_project_test.go b/tests/integration/private_project_test.go index b2ad8d1cd7..ea6788cb64 100644 --- a/tests/integration/private_project_test.go +++ b/tests/integration/private_project_test.go @@ -18,7 +18,7 @@ import ( ) func TestPrivateIssueProject(t *testing.T) { - defer tests.AddFixtures("models/fixtures/PrivateIssueProjects/")() + defer unittest.OverrideFixtures("models/fixtures/PrivateIssueProjects")() defer tests.PrepareTestEnv(t)() user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index 6dacdc4315..4be493b196 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -652,7 +652,7 @@ func getUserNotificationCount(t *testing.T, session *TestSession, csrf string) s } func TestPullRequestReplyMail(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestPullRequestReplyMail/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestPullRequestReplyMail")() defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/pull_test.go b/tests/integration/pull_test.go index 3695ebe5f2..d5321f6ae5 100644 --- a/tests/integration/pull_test.go +++ b/tests/integration/pull_test.go @@ -67,7 +67,7 @@ func TestPullManuallyMergeWarning(t *testing.T) { } func TestPullCombinedReviewRequest(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestPullCombinedReviewRequest/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestPullCombinedReviewRequest")() defer tests.PrepareTestEnv(t)() session := loginUser(t, "user2") diff --git a/tests/integration/runner_test.go b/tests/integration/runner_test.go index 2ca0a6b996..09555caacf 100644 --- a/tests/integration/runner_test.go +++ b/tests/integration/runner_test.go @@ -19,7 +19,7 @@ import ( ) func TestRunnerModification(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestRunnerModification")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestRunnerModification")() defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/xss_test.go b/tests/integration/xss_test.go index 2c4934c541..94cbeaf331 100644 --- a/tests/integration/xss_test.go +++ b/tests/integration/xss_test.go @@ -68,7 +68,7 @@ func TestXSSWikiLastCommitInfo(t *testing.T) { } func TestXSSReviewDismissed(t *testing.T) { - defer tests.AddFixtures("tests/integration/fixtures/TestXSSReviewDismissed/")() + defer unittest.OverrideFixtures("tests/integration/fixtures/TestXSSReviewDismissed")() defer tests.PrepareTestEnv(t)() review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 1000}) diff --git a/tests/test_utils.go b/tests/test_utils.go index e6aa9c7963..b338ddc052 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -337,16 +337,6 @@ func Printf(format string, args ...any) { testlogger.Printf(format, args...) } -func AddFixtures(dirs ...string) func() { - return unittest.OverrideFixtures( - unittest.FixturesOptions{ - Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), - Base: setting.AppWorkPath, - Dirs: dirs, - }, - ) -} - type DeclarativeRepoOptions struct { Name optional.Option[string] EnabledUnits optional.Option[[]unit_model.Type] From 16c0285ca466b5c454fbacd34628e8e97d4a4a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Tue, 30 Apr 2024 12:33:18 +0200 Subject: [PATCH 011/115] Fix name in package-lock.json The frontend-checks job started failing because the declared name does not match the repository name. --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index dfd359b31b..318521d1ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "forgejo", + "name": "forgejo-aneksajo", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "forgejo", + "name": "forgejo-aneksajo", "dependencies": { "@citation-js/core": "0.7.14", "@citation-js/plugin-bibtex": "0.7.16", diff --git a/package.json b/package.json index 75739b3be0..c79d20abbc 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "forgejo", + "name": "forgejo-aneksajo", "type": "module", "engines": { "node": ">= 20.0.0" From d9987529035a25736cb069fab7d92d3317997d14 Mon Sep 17 00:00:00 2001 From: Nick Guenther Date: Sat, 30 Jul 2022 21:45:03 -0400 Subject: [PATCH 012/115] git-annex support [git-annex](https://git-annex.branchable.com/) is a more complicated cousin to git-lfs, storing large files in an optional-download side content. Unlike lfs, it allows mixing and matching storage remotes, so the content remote(s) doesn't need to be on the same server as the git remote, making it feasible to scatter a collection across cloud storage, old harddrives, or anywhere else storage can be scavenged. Since this can get complicated, fast, it has a content-tracking database (`git annex whereis`) to help find everything later. The use-case we imagine for including it in Gitea is just the simple case, where we're primarily emulating git-lfs: each repo has its large content at the same URL. Our motivation is so we can self-host https://www.datalad.org/ datasets, which currently are only hostable by fragilely scrounging together cloud storage -- and having to manage all the credentials associated with all the pieces -- or at https://openneuro.org which is fragile in its own ways. Supporting git-annex also allows multiple Gitea instance to be annex remotes for each other, mirroring the content or otherwise collaborating the split up the hosting costs. Enabling -------- TODO HTTP ---- TODO Permission Checking ------------------- This tweaks the API in routers/private/serv.go to expose the calling user's computed permission, instead of just returning HTTP 403. This doesn't fit in super well. It's the opposite from how the git-lfs support is done, where there's a complete list of possible subcommands and their matching permission levels, and then the API compares the requested with the actual level and returns HTTP 403 if the check fails. But it's necessary. The main git-annex verbs, 'git-annex-shell configlist' and 'git-annex-shell p2pstdio' are both either read-only or read-write operations, depending on the state on disk on either end of the connection and what the user asked it to ask for, with no way to know before git-annex examines the situation. So tell the level via GIT_ANNEX_READONLY and trust it to handle itself. In the older Gogs version, the permission was directly read in cmd/serv.go: ``` mode, err = db.UserAccessMode(user.ID, repo) ``` - https://github.com/G-Node/gogs/blob/966e925cf320beff768b192276774d9265706df5/internal/cmd/serv.go#L334 but in Gitea permission enforcement has been centralized in the API layer. (perhaps so the cmd layer can avoid making direct DB connections?) Deletion -------- git-annex has this "lockdown" feature where it tries really quite very hard to prevent you deleting its data, to the point that even an rm -rf won't do it: each file in annex/objects/ is nested inside a folder with read-only permissions. The recommended workaround is to run chmod -R +w when you're sure you actually want to delete a repo. See https://git-annex.branchable.com/internals/lockdown So we edit util.RemoveAll() to do just that, so now it's `chmod -R +w && rm -rf` instead of just `rm -rf`. --- cmd/serv.go | 72 ++++++++++++++++++++++++++++++++++++++--- modules/private/serv.go | 1 + modules/util/remove.go | 34 ++++++++++++++++++- routers/private/serv.go | 12 +++++-- 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/cmd/serv.go b/cmd/serv.go index 0884d6c36b..59379a91b4 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -38,6 +38,7 @@ import ( const ( lfsAuthenticateVerb = "git-lfs-authenticate" + gitAnnexShellVerb = "git-annex-shell" ) // CmdServ represents the available serv sub-command. @@ -79,6 +80,7 @@ var ( "git-upload-archive": perm.AccessModeRead, "git-receive-pack": perm.AccessModeWrite, lfsAuthenticateVerb: perm.AccessModeNone, + gitAnnexShellVerb: perm.AccessModeNone, // annex permissions are enforced by GIT_ANNEX_SHELL_READONLY, rather than the Gitea API } alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`) ) @@ -212,6 +214,29 @@ func runServ(c *cli.Context) error { } } + if verb == gitAnnexShellVerb { + // if !setting.Annex.Enabled { // TODO: https://github.com/neuropoly/gitea/issues/8 + if false { + return fail(ctx, "Unknown git command", "git-annex request over SSH denied, git-annex support is disabled") + } + + if len(words) < 3 { + return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) + } + + // git-annex always puts the repo in words[2], unlike most other + // git subcommands; and it sometimes names repos like /~/, as if + // $HOME should get expanded while also being rooted. e.g.: + // git-annex-shell 'configlist' '/~/user/repo' + // git-annex-shell 'sendkey' '/user/repo 'key' + repoPath = words[2] + repoPath = strings.TrimPrefix(repoPath, "/") + repoPath = strings.TrimPrefix(repoPath, "~/") + } + + // prevent directory traversal attacks + repoPath = filepath.Clean("/" + repoPath)[1:] + rr := strings.SplitN(repoPath, "/", 2) if len(rr) != 2 { return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) @@ -225,6 +250,18 @@ func runServ(c *cli.Context) error { // so that username and reponame are not affected. repoPath = strings.ToLower(strings.TrimSpace(repoPath)) + // put the sanitized repoPath back into the argument list for later + if verb == gitAnnexShellVerb { + // git-annex-shell demands an absolute path + absRepoPath, err := filepath.Abs(filepath.Join(setting.RepoRootPath, repoPath)) + if err != nil { + return fail(ctx, "Error locating repoPath", "%v", err) + } + words[2] = absRepoPath + } else { + words[1] = repoPath + } + if alphaDashDotPattern.MatchString(reponame) { return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) } @@ -303,21 +340,46 @@ func runServ(c *cli.Context) error { return nil } - var gitcmd *exec.Cmd gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack if _, err := os.Stat(gitBinVerb); err != nil { // if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git // ps: Windows only has "git.exe" in the bin path, so Windows always uses this way + // ps: git-annex-shell and other extensions may not necessarily be in gitBinPath, + // but '{gitBinPath}/git annex-shell' should be able to find them on $PATH. verbFields := strings.SplitN(verb, "-", 2) if len(verbFields) == 2 { // use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ... - gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath) + gitBinVerb = git.GitExecutable + words = append([]string{verbFields[1]}, words...) } } - if gitcmd == nil { - // by default, use the verb (it has been checked above by allowedCommands) - gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath) + + // by default, use the verb (it has been checked above by allowedCommands) + gitcmd := exec.CommandContext(ctx, gitBinVerb, words[1:]...) + + if verb == gitAnnexShellVerb { + // This doesn't get its own isolated section like LFS does, because LFS + // is handled by internal Gitea routines, but git-annex has to be shelled out + // to like other git subcommands, so we need to build up gitcmd. + + // TODO: does this work on Windows? + gitcmd.Env = append(gitcmd.Env, + // "If set, disallows running git-shell to handle unknown commands." + // - git-annex-shell(1) + "GIT_ANNEX_SHELL_LIMITED=True", + // "If set, git-annex-shell will refuse to run commands + // that do not operate on the specified directory." + // - git-annex-shell(1) + fmt.Sprintf("GIT_ANNEX_SHELL_DIRECTORY=%s", words[2]), + ) + if results.UserMode < perm.AccessModeWrite { + // "If set, disallows any action that could modify the git-annex repository." + // - git-annex-shell(1) + // We set this when the backend API has told us that we don't have write permission to this repo. + log.Debug("Setting GIT_ANNEX_SHELL_READONLY=True") + gitcmd.Env = append(gitcmd.Env, "GIT_ANNEX_SHELL_READONLY=True") + } } process.SetSysProcAttribute(gitcmd) diff --git a/modules/private/serv.go b/modules/private/serv.go index fb8496930e..af4a016cb8 100644 --- a/modules/private/serv.go +++ b/modules/private/serv.go @@ -40,6 +40,7 @@ type ServCommandResults struct { UserName string UserEmail string UserID int64 + UserMode perm.AccessMode OwnerName string RepoName string RepoID int64 diff --git a/modules/util/remove.go b/modules/util/remove.go index 2a65a6b0aa..0d0323d0b4 100644 --- a/modules/util/remove.go +++ b/modules/util/remove.go @@ -4,7 +4,9 @@ package util import ( + "io/fs" "os" + "path/filepath" "syscall" "time" ) @@ -32,10 +34,40 @@ func Remove(name string) error { return err } -// RemoveAll removes the named file or (empty) directory with at most 5 attempts. +// RemoveAll removes the named file or directory with at most 5 attempts. func RemoveAll(name string) error { var err error + for i := 0; i < 5; i++ { + // Do chmod -R +w to help ensure the removal succeeds. + // In particular, in the git-annex case, this handles + // https://git-annex.branchable.com/internals/lockdown/ : + // + // > (The only bad consequence of this is that rm -rf .git + // > doesn't work unless you first run chmod -R +w .git) + + err = filepath.WalkDir(name, func(path string, d fs.DirEntry, err error) error { + // NB: this is called WalkDir but it works on a single file too + if err == nil { + info, err := d.Info() + if err != nil { + return err + } + + // 0200 == u+w, in octal unix permission notation + err = os.Chmod(path, info.Mode()|0o200) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + // try again + <-time.After(100 * time.Millisecond) + continue + } + err = os.RemoveAll(name) if err == nil { break diff --git a/routers/private/serv.go b/routers/private/serv.go index df61355fb0..82953b5af9 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -81,12 +81,14 @@ func ServCommand(ctx *context.PrivateContext) { ownerName := ctx.Params(":owner") repoName := ctx.Params(":repo") mode := perm.AccessMode(ctx.FormInt("mode")) + verbs := ctx.FormStrings("verb") // Set the basic parts of the results to return results := private.ServCommandResults{ RepoName: repoName, OwnerName: ownerName, KeyID: keyID, + UserMode: perm.AccessModeNone, } // Now because we're not translating things properly let's just default some English strings here @@ -287,8 +289,10 @@ func ServCommand(ctx *context.PrivateContext) { repo.IsPrivate || owner.Visibility.IsPrivate() || (user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey + ( /*setting.Annex.Enabled && */ len(verbs) > 0 && verbs[0] == "git-annex-shell") || // git-annex has its own permission enforcement, for which we expose results.UserMode setting.Service.RequireSignInView) { if key.Type == asymkey_model.KeyTypeDeploy { + results.UserMode = deployKey.Mode if deployKey.Mode < mode { ctx.JSON(http.StatusUnauthorized, private.Response{ UserMsg: fmt.Sprintf("Deploy Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName), @@ -310,9 +314,9 @@ func ServCommand(ctx *context.PrivateContext) { return } - userMode := perm.UnitAccessMode(unitType) + results.UserMode = perm.UnitAccessMode(unitType) - if userMode < mode { + if results.UserMode < mode { log.Warn("Failed authentication attempt for %s with key %s (not authorized to %s %s/%s) from %s", user.Name, key.Name, modeString, ownerName, repoName, ctx.RemoteAddr()) ctx.JSON(http.StatusUnauthorized, private.Response{ UserMsg: fmt.Sprintf("User: %d:%s with Key: %d:%s is not authorized to %s %s/%s.", user.ID, user.Name, key.ID, key.Name, modeString, ownerName, repoName), @@ -353,6 +357,7 @@ func ServCommand(ctx *context.PrivateContext) { }) return } + results.UserMode = perm.AccessModeWrite results.RepoID = repo.ID } @@ -381,13 +386,14 @@ func ServCommand(ctx *context.PrivateContext) { return } } - log.Debug("Serv Results:\nIsWiki: %t\nDeployKeyID: %d\nKeyID: %d\tKeyName: %s\nUserName: %s\nUserID: %d\nOwnerName: %s\nRepoName: %s\nRepoID: %d", + log.Debug("Serv Results:\nIsWiki: %t\nDeployKeyID: %d\nKeyID: %d\tKeyName: %s\nUserName: %s\nUserID: %d\nUserMode: %d\nOwnerName: %s\nRepoName: %s\nRepoID: %d", results.IsWiki, results.DeployKeyID, results.KeyID, results.KeyName, results.UserName, results.UserID, + results.UserMode, results.OwnerName, results.RepoName, results.RepoID) From 1d53f9f2cdcba63f7fd76ba4c27f4d876a03a48e Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 19 Aug 2022 02:49:18 -0400 Subject: [PATCH 013/115] git-annex tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/neuropoly/gitea/issues/11 Tests: * `git annex init` * `git annex copy --from origin` * `git annex copy --to origin` over: * ssh for: * the owner * a collaborator * a read-only collaborator * a stranger in a * public repo * private repo And then confirms: * Deletion of the remote repo (to ensure lockdown isn't messing with us: https://git-annex.branchable.com/internals/lockdown/#comment-0cc5225dc5abe8eddeb843bfd2fdc382) ------ To support all this: * Add util.FileCmp() * Patch withKeyFile() so it can be nested in other copies of itself ------- Many thanks to Mathieu for giving style tips and catching several bugs, including a subtle one in util.filecmp() which neutered it. Co-authored-by: Mathieu Guay-Paquet Co-authored-by: Matthias Riße --- Makefile | 2 +- modules/util/filecmp.go | 87 ++ modules/util/remove.go | 37 +- .../api_helper_for_declarative_test.go | 25 + tests/integration/git_annex_test.go | 760 ++++++++++++++++++ .../git_helper_for_declarative_test.go | 22 + 6 files changed, 916 insertions(+), 17 deletions(-) create mode 100644 modules/util/filecmp.go create mode 100644 tests/integration/git_annex_test.go diff --git a/Makefile b/Makefile index 4fb06db918..66f8524c04 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ self := $(location) @tmpdir=`mktemp --tmpdir -d` ; \ echo Using temporary directory $$tmpdir for test repositories ; \ USE_REPO_TEST_DIR= $(MAKE) -f $(self) --no-print-directory REPO_TEST_DIR=$$tmpdir/ $@ ; \ - STATUS=$$? ; rm -r "$$tmpdir" ; exit $$STATUS + STATUS=$$? ; chmod -R +w "$$tmpdir" && rm -r "$$tmpdir" ; exit $$STATUS else diff --git a/modules/util/filecmp.go b/modules/util/filecmp.go new file mode 100644 index 0000000000..76e7705cc1 --- /dev/null +++ b/modules/util/filecmp.go @@ -0,0 +1,87 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package util + +import ( + "bytes" + "io" + "os" +) + +// Decide if two files have the same contents or not. +// chunkSize is the size of the blocks to scan by; pass 0 to get a sensible default. +// *Follows* symlinks. +// +// May return an error if something else goes wrong; in this case, you should ignore the value of 'same'. +// +// derived from https://stackoverflow.com/a/30038571 +// under CC-BY-SA-4.0 by several contributors +func FileCmp(file1, file2 string, chunkSize int) (same bool, err error) { + if chunkSize == 0 { + chunkSize = 4 * 1024 + } + + // shortcuts: check file metadata + stat1, err := os.Stat(file1) + if err != nil { + return false, err + } + + stat2, err := os.Stat(file2) + if err != nil { + return false, err + } + + // are inputs are literally the same file? + if os.SameFile(stat1, stat2) { + return true, nil + } + + // do inputs at least have the same size? + if stat1.Size() != stat2.Size() { + return false, nil + } + + // long way: compare contents + f1, err := os.Open(file1) + if err != nil { + return false, err + } + defer f1.Close() + + f2, err := os.Open(file2) + if err != nil { + return false, err + } + defer f2.Close() + + b1 := make([]byte, chunkSize) + b2 := make([]byte, chunkSize) + for { + n1, err1 := io.ReadFull(f1, b1) + n2, err2 := io.ReadFull(f2, b2) + + // https://pkg.go.dev/io#Reader + // > Callers should always process the n > 0 bytes returned + // > before considering the error err. Doing so correctly + // > handles I/O errors that happen after reading some bytes + // > and also both of the allowed EOF behaviors. + + if !bytes.Equal(b1[:n1], b2[:n2]) { + return false, nil + } + + if (err1 == io.EOF && err2 == io.EOF) || (err1 == io.ErrUnexpectedEOF && err2 == io.ErrUnexpectedEOF) { + return true, nil + } + + // some other error, like a dropped network connection or a bad transfer + if err1 != nil { + return false, err1 + } + if err2 != nil { + return false, err2 + } + } +} diff --git a/modules/util/remove.go b/modules/util/remove.go index 0d0323d0b4..4e5271136d 100644 --- a/modules/util/remove.go +++ b/modules/util/remove.go @@ -34,6 +34,26 @@ func Remove(name string) error { return err } +// MakeWritable recursively makes the named directory writable. +func MakeWritable(name string) error { + return filepath.WalkDir(name, func(path string, d fs.DirEntry, err error) error { + // NB: this is called WalkDir but it works on a single file too + if err == nil { + info, err := d.Info() + if err != nil { + return err + } + + // 0200 == u+w, in octal unix permission notation + err = os.Chmod(path, info.Mode()|0o200) + if err != nil { + return err + } + } + return nil + }) +} + // RemoveAll removes the named file or directory with at most 5 attempts. func RemoveAll(name string) error { var err error @@ -46,22 +66,7 @@ func RemoveAll(name string) error { // > (The only bad consequence of this is that rm -rf .git // > doesn't work unless you first run chmod -R +w .git) - err = filepath.WalkDir(name, func(path string, d fs.DirEntry, err error) error { - // NB: this is called WalkDir but it works on a single file too - if err == nil { - info, err := d.Info() - if err != nil { - return err - } - - // 0200 == u+w, in octal unix permission notation - err = os.Chmod(path, info.Mode()|0o200) - if err != nil { - return err - } - } - return nil - }) + err = MakeWritable(name) if err != nil { // try again <-time.After(100 * time.Millisecond) diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index b5941b3389..88e1cd2198 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -21,6 +21,7 @@ import ( api "forgejo.org/modules/structs" "forgejo.org/services/forms" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -460,3 +461,27 @@ func doAPIAddRepoToOrganizationTeam(ctx APITestContext, teamID int64, orgName, r ctx.Session.MakeRequest(t, req, http.StatusNoContent) } } + +// generate and activate an ssh key for the user attached to the APITestContext +// TODO: pick a better name; golang doesn't do method overriding. +func withCtxKeyFile(t *testing.T, ctx APITestContext, callback func()) { + // we need to have write:public_key to do this step + // the easiest way is to create a throwaway ctx that is identical but only has that permission + ctxKeyWriter := ctx + ctxKeyWriter.Token = getTokenForLoggedInUser(t, ctx.Session, auth.AccessTokenScopeWriteUser) + + keyName := "One of " + ctx.Username + "'s keys: #" + uuid.New().String() + withKeyFile(t, keyName, func(keyFile string) { + var key api.PublicKey + + doAPICreateUserKey(ctxKeyWriter, keyName, keyFile, + func(t *testing.T, _key api.PublicKey) { + // save the key ID so we can delete it at the end + key = _key + })(t) + + defer doAPIDeleteUserKey(ctxKeyWriter, key.ID)(t) + + callback() + }) +} diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go new file mode 100644 index 0000000000..f64b1d98df --- /dev/null +++ b/tests/integration/git_annex_test.go @@ -0,0 +1,760 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integration + +import ( + "errors" + "fmt" + "math/rand" + "net/url" + "os" + "path" + "regexp" + "strings" + "testing" + + auth_model "forgejo.org/models/auth" + "forgejo.org/models/db" + "forgejo.org/models/perm" + repo_model "forgejo.org/models/repo" + "forgejo.org/modules/git" + "forgejo.org/modules/setting" + api "forgejo.org/modules/structs" + "forgejo.org/modules/util" + "forgejo.org/tests" + + "github.com/stretchr/testify/require" +) + +// Some guidelines: +// +// * a APITestContext is an awkward union of session credential + username + target repo +// which is assumed to be owned by that username; if you want to target a different +// repo, you need to edit its .Reponame or just ignore it and write "username/reponame.git" + +func doCreateRemoteAnnexRepository(t *testing.T, u *url.URL, ctx APITestContext, private bool) (err error) { + // creating a repo counts as editing the user's profile (is done by POSTing + // to /api/v1/user/repos/) -- which means it needs a User-scoped token and + // both that and editing need a Repo-scoped token because they edit repositories. + rescopedCtx := ctx + rescopedCtx.Token = getTokenForLoggedInUser(t, ctx.Session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + doAPICreateRepository(rescopedCtx, false)(t) + t.Cleanup(func() { util.MakeWritable(setting.RepoRootPath) }) + doAPIEditRepository(rescopedCtx, &api.EditRepoOption{Private: &private})(t) + + repoURL := createSSHUrl(ctx.GitPath(), u) + + // Fill in fixture data + withAnnexCtxKeyFile(t, ctx, func() { + err = doInitRemoteAnnexRepository(t, repoURL) + }) + if err != nil { + return fmt.Errorf("Unable to initialize remote repo with git-annex fixture: %w", err) + } + return nil +} + +/* +Test that permissions are enforced on git-annex-shell commands. + + Along the way, test that uploading, downloading, and deleting all work. +*/ +func TestGitAnnexPermissions(t *testing.T) { + /* + // TODO: look into how LFS did this + if !setting.Annex.Enabled { + t.Skip() + } + */ + + // Each case below is split so that 'clone' is done as + // the repo owner, but 'copy' as the user under test. + // + // Otherwise, in cases where permissions block the + // initial 'clone', the test would simply end there + // and never verify if permissions apply properly to + // 'annex copy' -- potentially leaving a security gap. + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + t.Run("Public", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + ownerCtx := NewAPITestContext(t, "user2", "annex-public", auth_model.AccessTokenScopeWriteRepository) + + // create a public repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ownerCtx, false)) + + // double-check it's public + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ownerCtx.Username, ownerCtx.Reponame) + require.NoError(t, err) + require.False(t, repo.IsPrivate) + + // Remote addresses of the repo + repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL + remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost + + // Different sessions, so we can test different permissions. + // We leave Reponame blank because we don't actually then later add it according to each case if needed + // + // NB: these usernames need to match appropriate entries in models/fixtures/user.yml + writerCtx := NewAPITestContext(t, "user5", "", auth_model.AccessTokenScopeWriteRepository) + readerCtx := NewAPITestContext(t, "user4", "", auth_model.AccessTokenScopeReadRepository) + outsiderCtx := NewAPITestContext(t, "user8", "", auth_model.AccessTokenScopeReadRepository) // a user with no specific access + + // set up collaborators + doAPIAddCollaborator(ownerCtx, readerCtx.Username, perm.AccessModeRead)(t) + doAPIAddCollaborator(ownerCtx, writerCtx.Username, perm.AccessModeWrite)(t) + + // tests + t.Run("Owner", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, ownerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + }) + }) + + t.Run("Writer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, writerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + }) + }) + + t.Run("Reader", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, readerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) + }) + }) + }) + + t.Run("Outsider", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, outsiderCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) + }) + }) + }) + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Delete the repo, make sure it's fully gone + doAPIDeleteRepository(ownerCtx)(t) + _, statErr := os.Stat(remoteRepoPath) + require.True(t, os.IsNotExist(statErr), "Remote annex repo should be removed from disk") + }) + }) + + t.Run("Private", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + ownerCtx := NewAPITestContext(t, "user2", "annex-private", auth_model.AccessTokenScopeWriteRepository) + + // create a private repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ownerCtx, true)) + + // double-check it's private + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ownerCtx.Username, ownerCtx.Reponame) + require.NoError(t, err) + require.True(t, repo.IsPrivate) + + // Remote addresses of the repo + repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL + remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost + + // Different sessions, so we can test different permissions. + // We leave Reponame blank because we don't actually then later add it according to each case if needed + // + // NB: these usernames need to match appropriate entries in models/fixtures/user.yml + writerCtx := NewAPITestContext(t, "user5", "", auth_model.AccessTokenScopeWriteRepository) + readerCtx := NewAPITestContext(t, "user4", "", auth_model.AccessTokenScopeReadRepository) + outsiderCtx := NewAPITestContext(t, "user8", "", auth_model.AccessTokenScopeReadRepository) // a user with no specific access + // Note: there's also full anonymous access, which is only available for public HTTP repos; + // it should behave the same as 'outsider' but we (will) test it separately below anyway + + // set up collaborators + doAPIAddCollaborator(ownerCtx, readerCtx.Username, perm.AccessModeRead)(t) + doAPIAddCollaborator(ownerCtx, writerCtx.Username, perm.AccessModeWrite)(t) + + // tests + t.Run("Owner", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, ownerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + }) + }) + + t.Run("Writer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, writerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + }) + }) + + t.Run("Reader", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, readerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) + }) + }) + }) + + t.Run("Outsider", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, outsiderCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath), "annex init should fail due to permissions") + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath), "annex copy --from should fail due to permissions") + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "annex copy --to should fail due to permissions") + }) + }) + }) + }) + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Delete the repo, make sure it's fully gone + doAPIDeleteRepository(ownerCtx)(t) + _, statErr := os.Stat(remoteRepoPath) + require.True(t, os.IsNotExist(statErr), "Remote annex repo should be removed from disk") + }) + }) + }) +} + +/* +Test that 'git annex init' works. + + precondition: repoPath contains a pre-cloned repo set up by doInitAnnexRepository(). +*/ +func doAnnexInitTest(remoteRepoPath, repoPath string) (err error) { + _, _, err = git.NewCommand(git.DefaultContext, "annex", "init", "cloned-repo").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return fmt.Errorf("Couldn't `git annex init`: %w", err) + } + + // - method 0: 'git config remote.origin.annex-uuid'. + // Demonstrates that 'git annex init' successfully contacted + // the remote git-annex and was able to learn its ID number. + readAnnexUUID, _, err := git.NewCommand(git.DefaultContext, "config", "remote.origin.annex-uuid").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return fmt.Errorf("Couldn't read remote `git config remote.origin.annex-uuid`: %w", err) + } + readAnnexUUID = strings.TrimSpace(readAnnexUUID) + + match := regexp.MustCompile("^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$").MatchString(readAnnexUUID) + if !match { + return fmt.Errorf("'git config remote.origin.annex-uuid' should have been able to download the remote's uuid; but instead read '%s'", readAnnexUUID) + } + + remoteAnnexUUID, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: remoteRepoPath}) + if err != nil { + return fmt.Errorf("Couldn't read local `git config annex.uuid`: %w", err) + } + + remoteAnnexUUID = strings.TrimSpace(remoteAnnexUUID) + match = regexp.MustCompile("^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$").MatchString(remoteAnnexUUID) + if !match { + return fmt.Errorf("'git annex init' should have been able to download the remote's uuid; but instead read '%s'", remoteAnnexUUID) + } + + if readAnnexUUID != remoteAnnexUUID { + return fmt.Errorf("'git annex init' should have read the expected annex UUID '%s', but instead got '%s'", remoteAnnexUUID, readAnnexUUID) + } + + // - method 1: 'git annex whereis'. + // Demonstrates that git-annex understands the annexed file can be found in the remote annex. + annexWhereis, _, err := git.NewCommand(git.DefaultContext, "annex", "whereis", "large.bin").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return fmt.Errorf("Couldn't `git annex whereis large.bin`: %w", err) + } + // Note: this regex is unanchored because 'whereis' outputs multiple lines containing + // headers and 1+ remotes and we just want to find one of them. + match = regexp.MustCompile(regexp.QuoteMeta(remoteAnnexUUID) + " -- .* \\[origin\\]\n").MatchString(annexWhereis) + if !match { + return errors.New("'git annex whereis' should report large.bin is known to be in [origin]") + } + + return nil +} + +func doAnnexDownloadTest(remoteRepoPath, repoPath string) (err error) { + // NB: this test does something slightly different if run separately from "doAnnexInitTest()": + // "git annex copy" will notice and run "git annex init", silently. + // This shouldn't change any results, but be aware in case it does. + + _, _, err = git.NewCommand(git.DefaultContext, "annex", "copy", "--from", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + + // verify the file was downloaded + localObjectPath, err := annexObjectPath(repoPath, "large.bin") + if err != nil { + return err + } + // localObjectPath := path.Join(repoPath, "large.bin") // or, just compare against the checked-out file + + remoteObjectPath, err := annexObjectPath(remoteRepoPath, "large.bin") + if err != nil { + return err + } + + match, err := util.FileCmp(localObjectPath, remoteObjectPath, 0) + if err != nil { + return err + } + if !match { + return errors.New("Annexed files should be the same") + } + + return nil +} + +func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { + // NB: this test does something slightly different if run separately from "Init": + // it first runs "git annex init" silently in the background. + // This shouldn't change any results, but be aware in case it does. + + err = generateRandomFile(1024*1024/4, path.Join(repoPath, "contribution.bin")) + if err != nil { + return err + } + + err = git.AddChanges(repoPath, false, ".") + if err != nil { + return err + } + + err = git.CommitChanges(repoPath, git.CommitChangesOptions{Message: "Annex another file"}) + if err != nil { + return err + } + + _, _, err = git.NewCommand(git.DefaultContext, "annex", "copy", "--to", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + + _, _, err = git.NewCommand(git.DefaultContext, "annex", "sync", "--no-content").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + + // verify the file was uploaded + localObjectPath, err := annexObjectPath(repoPath, "contribution.bin") + if err != nil { + return err + } + // localObjectPath := path.Join(repoPath, "contribution.bin") // or, just compare against the checked-out file + + remoteObjectPath, err := annexObjectPath(remoteRepoPath, "contribution.bin") + if err != nil { + return err + } + + match, err := util.FileCmp(localObjectPath, remoteObjectPath, 0) + if err != nil { + return err + } + if !match { + return errors.New("Annexed files should be the same") + } + + return nil +} + +// ---- Helpers ---- + +func generateRandomFile(size int, path string) (err error) { + // Generate random file + + // XXX TODO: maybe this should not be random, but instead a predictable pattern, so that the test is deterministic + bufSize := 4 * 1024 + if bufSize > size { + bufSize = size + } + + buffer := make([]byte, bufSize) + + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + + written := 0 + for written < size { + n := size - written + if n > bufSize { + n = bufSize + } + _, err := rand.Read(buffer[:n]) + if err != nil { + return err + } + n, err = f.Write(buffer[:n]) + if err != nil { + return err + } + written += n + } + if err != nil { + return err + } + + return nil +} + +// ---- Annex-specific helpers ---- + +/* +Initialize a repo with some baseline annexed and non-annexed files. + + TODO: perhaps this generator could be replaced with a fixture (see + integrations/gitea-repositories-meta/ and models/fixtures/repository.yml). + However we reuse this template for -different- repos, so maybe not. +*/ +func doInitAnnexRepository(repoPath string) error { + // set up what files should be annexed + // in this case, all *.bin files will be annexed + // without this, git-annex's default config annexes every file larger than some number of megabytes + f, err := os.Create(path.Join(repoPath, ".gitattributes")) + if err != nil { + return err + } + defer f.Close() + + // set up git-annex to store certain filetypes via *annex* pointers + // (https://git-annex.branchable.com/internals/pointer_file/). + // but only when run via 'git add' (see git-annex-smudge(1)) + _, err = f.WriteString("* annex.largefiles=anything\n") + if err != nil { + return err + } + _, err = f.WriteString("*.bin filter=annex\n") + if err != nil { + return err + } + f.Close() + + err = git.AddChanges(repoPath, false, ".") + if err != nil { + return err + } + err = git.CommitChanges(repoPath, git.CommitChangesOptions{Message: "Configure git-annex settings"}) + if err != nil { + return err + } + + // 'git annex init' + err = git.NewCommand(git.DefaultContext, "annex", "init", "test-repo").Run(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + + // add a file to the annex + err = generateRandomFile(1024*1024/4, path.Join(repoPath, "large.bin")) + if err != nil { + return err + } + err = git.AddChanges(repoPath, false, ".") + if err != nil { + return err + } + err = git.CommitChanges(repoPath, git.CommitChangesOptions{Message: "Annex a file"}) + if err != nil { + return err + } + + return nil +} + +/* +Initialize a remote repo with some baseline annexed and non-annexed files. +*/ +func doInitRemoteAnnexRepository(t *testing.T, repoURL *url.URL) error { + repoPath := path.Join(t.TempDir(), path.Base(repoURL.Path)) + // This clone is immediately thrown away, which + // helps force the tests to be end-to-end. + defer util.RemoveAll(repoPath) + + doGitClone(repoPath, repoURL)(t) // TODO: this call is the only reason for the testing.T; can it be removed? + + err := doInitAnnexRepository(repoPath) + if err != nil { + return err + } + + _, _, err = git.NewCommand(git.DefaultContext, "annex", "sync", "--content").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + + return nil +} + +/* +Find the path in .git/annex/objects/ of the contents for a given annexed file. + + repoPath: the git repository to examine + file: the path (in the repo's current HEAD) of the annex pointer + + TODO: pass a parameter to allow examining non-HEAD branches +*/ +func annexObjectPath(repoPath, file string) (string, error) { + // NB: `git annex lookupkey` is more reliable, but doesn't work in bare repos. + annexKey, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "show").AddDynamicArguments("HEAD:" + file).RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return "", fmt.Errorf("in %s: %w", repoPath, err) // the error from git prints the filename but not repo + } + + // There are two formats an annexed file pointer might be: + // * a symlink to .git/annex/objects/$HASHDIR/$ANNEX_KEY/$ANNEX_KEY - used by files created with 'git annex add' + // * a text file containing /annex/objects/$ANNEX_KEY - used by files for which 'git add' was configured to run git-annex-smudge + // This recovers $ANNEX_KEY from either case: + annexKey = path.Base(strings.TrimSpace(annexKey)) + + contentPath, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(annexKey).RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return "", fmt.Errorf("in %s: %s does not seem to be annexed: %w", repoPath, file, err) + } + contentPath = strings.TrimSpace(contentPath) + + return path.Join(repoPath, contentPath), nil +} + +/* like withKeyFile(), but automatically sets it the account given in ctx for use by git-annex */ +func withAnnexCtxKeyFile(t *testing.T, ctx APITestContext, callback func()) { + _gitAnnexUseGitSSH, gitAnnexUseGitSSHExists := os.LookupEnv("GIT_ANNEX_USE_GIT_SSH") + defer func() { + // reset + if gitAnnexUseGitSSHExists { + os.Setenv("GIT_ANNEX_USE_GIT_SSH", _gitAnnexUseGitSSH) + } + }() + + os.Setenv("GIT_ANNEX_USE_GIT_SSH", "1") // withKeyFile works by setting GIT_SSH_COMMAND, but git-annex only respects that if this is set + + withCtxKeyFile(t, ctx, callback) +} diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 89453296ca..8921768c1f 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -42,6 +42,28 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) { "ssh -o \"UserKnownHostsFile=/dev/null\" -o \"StrictHostKeyChecking=no\" -o \"IdentitiesOnly=yes\" -i \""+keyFile+"\" \"$@\""), 0o700) require.NoError(t, err) + // reset ssh wrapper afterwards + _gitSSH, gitSSHExists := os.LookupEnv("GIT_SSH") + defer func() { + if gitSSHExists { + t.Setenv("GIT_SSH", _gitSSH) + } + }() + + _gitSSHCommand, gitSSHCommandExists := os.LookupEnv("GIT_SSH_COMMAND") + defer func() { + if gitSSHCommandExists { + t.Setenv("GIT_SSH_COMMAND", _gitSSHCommand) + } + }() + + _gitSSHVariant, gitSSHVariantExists := os.LookupEnv("GIT_SSH_VARIANT") + defer func() { + if gitSSHVariantExists { + t.Setenv("GIT_SSH_VARIANT", _gitSSHVariant) + } + }() + // Setup ssh wrapper t.Setenv("GIT_SSH", path.Join(tmpDir, "ssh")) t.Setenv("GIT_SSH_COMMAND", From c821e35ce3b368f7c0d23c6c472bde93e8cf9149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 24 May 2024 12:57:45 +0200 Subject: [PATCH 014/115] Adapt patch to upstream changes Repository creation now expects an objectFormat to be specified for git. --- tests/integration/git_annex_test.go | 522 ++++++++++++++-------------- 1 file changed, 262 insertions(+), 260 deletions(-) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index f64b1d98df..4a2f255737 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -34,13 +34,13 @@ import ( // which is assumed to be owned by that username; if you want to target a different // repo, you need to edit its .Reponame or just ignore it and write "username/reponame.git" -func doCreateRemoteAnnexRepository(t *testing.T, u *url.URL, ctx APITestContext, private bool) (err error) { +func doCreateRemoteAnnexRepository(t *testing.T, u *url.URL, ctx APITestContext, private bool, objectFormat git.ObjectFormat) (err error) { // creating a repo counts as editing the user's profile (is done by POSTing // to /api/v1/user/repos/) -- which means it needs a User-scoped token and // both that and editing need a Repo-scoped token because they edit repositories. rescopedCtx := ctx rescopedCtx.Token = getTokenForLoggedInUser(t, ctx.Session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - doAPICreateRepository(rescopedCtx, false)(t) + doAPICreateRepository(rescopedCtx, false, objectFormat)(t) t.Cleanup(func() { util.MakeWritable(setting.RepoRootPath) }) doAPIEditRepository(rescopedCtx, &api.EditRepoOption{Private: &private})(t) @@ -78,365 +78,367 @@ func TestGitAnnexPermissions(t *testing.T) { // 'annex copy' -- potentially leaving a security gap. onGiteaRun(t, func(t *testing.T, u *url.URL) { - t.Run("Public", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - ownerCtx := NewAPITestContext(t, "user2", "annex-public", auth_model.AccessTokenScopeWriteRepository) - - // create a public repo - require.NoError(t, doCreateRemoteAnnexRepository(t, u, ownerCtx, false)) - - // double-check it's public - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ownerCtx.Username, ownerCtx.Reponame) - require.NoError(t, err) - require.False(t, repo.IsPrivate) - - // Remote addresses of the repo - repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL - remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost - - // Different sessions, so we can test different permissions. - // We leave Reponame blank because we don't actually then later add it according to each case if needed - // - // NB: these usernames need to match appropriate entries in models/fixtures/user.yml - writerCtx := NewAPITestContext(t, "user5", "", auth_model.AccessTokenScopeWriteRepository) - readerCtx := NewAPITestContext(t, "user4", "", auth_model.AccessTokenScopeReadRepository) - outsiderCtx := NewAPITestContext(t, "user8", "", auth_model.AccessTokenScopeReadRepository) // a user with no specific access - - // set up collaborators - doAPIAddCollaborator(ownerCtx, readerCtx.Username, perm.AccessModeRead)(t) - doAPIAddCollaborator(ownerCtx, writerCtx.Username, perm.AccessModeWrite)(t) - - // tests - t.Run("Owner", func(t *testing.T) { + forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { + t.Run("Public", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - t.Run("SSH", func(t *testing.T) { + ownerCtx := NewAPITestContext(t, "user2", "annex-public"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository) + + // create a public repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ownerCtx, false, objectFormat)) + + // double-check it's public + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ownerCtx.Username, ownerCtx.Reponame) + require.NoError(t, err) + require.False(t, repo.IsPrivate) + + // Remote addresses of the repo + repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL + remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost + + // Different sessions, so we can test different permissions. + // We leave Reponame blank because we don't actually then later add it according to each case if needed + // + // NB: these usernames need to match appropriate entries in models/fixtures/user.yml + writerCtx := NewAPITestContext(t, "user5", "", auth_model.AccessTokenScopeWriteRepository) + readerCtx := NewAPITestContext(t, "user4", "", auth_model.AccessTokenScopeReadRepository) + outsiderCtx := NewAPITestContext(t, "user8", "", auth_model.AccessTokenScopeReadRepository) // a user with no specific access + + // set up collaborators + doAPIAddCollaborator(ownerCtx, readerCtx.Username, perm.AccessModeRead)(t) + doAPIAddCollaborator(ownerCtx, writerCtx.Username, perm.AccessModeWrite)(t) + + // tests + t.Run("Owner", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - withAnnexCtxKeyFile(t, ownerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) }) }) }) - }) - t.Run("Writer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("SSH", func(t *testing.T) { + t.Run("Writer", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - withAnnexCtxKeyFile(t, writerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) }) }) }) - }) - t.Run("Reader", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("SSH", func(t *testing.T) { + t.Run("Reader", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - withAnnexCtxKeyFile(t, readerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) }) }) }) - }) - t.Run("Outsider", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("SSH", func(t *testing.T) { + t.Run("Outsider", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - withAnnexCtxKeyFile(t, outsiderCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) }) }) }) - }) - t.Run("Delete", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - // Delete the repo, make sure it's fully gone - doAPIDeleteRepository(ownerCtx)(t) - _, statErr := os.Stat(remoteRepoPath) - require.True(t, os.IsNotExist(statErr), "Remote annex repo should be removed from disk") - }) - }) - - t.Run("Private", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - ownerCtx := NewAPITestContext(t, "user2", "annex-private", auth_model.AccessTokenScopeWriteRepository) - - // create a private repo - require.NoError(t, doCreateRemoteAnnexRepository(t, u, ownerCtx, true)) - - // double-check it's private - repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ownerCtx.Username, ownerCtx.Reponame) - require.NoError(t, err) - require.True(t, repo.IsPrivate) - - // Remote addresses of the repo - repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL - remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost - - // Different sessions, so we can test different permissions. - // We leave Reponame blank because we don't actually then later add it according to each case if needed - // - // NB: these usernames need to match appropriate entries in models/fixtures/user.yml - writerCtx := NewAPITestContext(t, "user5", "", auth_model.AccessTokenScopeWriteRepository) - readerCtx := NewAPITestContext(t, "user4", "", auth_model.AccessTokenScopeReadRepository) - outsiderCtx := NewAPITestContext(t, "user8", "", auth_model.AccessTokenScopeReadRepository) // a user with no specific access - // Note: there's also full anonymous access, which is only available for public HTTP repos; - // it should behave the same as 'outsider' but we (will) test it separately below anyway - - // set up collaborators - doAPIAddCollaborator(ownerCtx, readerCtx.Username, perm.AccessModeRead)(t) - doAPIAddCollaborator(ownerCtx, writerCtx.Username, perm.AccessModeWrite)(t) - - // tests - t.Run("Owner", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("SSH", func(t *testing.T) { + t.Run("Delete", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) - - withAnnexCtxKeyFile(t, ownerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) - }) - - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) - }) - }) + // Delete the repo, make sure it's fully gone + doAPIDeleteRepository(ownerCtx)(t) + _, statErr := os.Stat(remoteRepoPath) + require.True(t, os.IsNotExist(statErr), "Remote annex repo should be removed from disk") }) }) - t.Run("Writer", func(t *testing.T) { + t.Run("Private", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - t.Run("SSH", func(t *testing.T) { + ownerCtx := NewAPITestContext(t, "user2", "annex-private"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository) + + // create a private repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ownerCtx, true, objectFormat)) + + // double-check it's private + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, ownerCtx.Username, ownerCtx.Reponame) + require.NoError(t, err) + require.True(t, repo.IsPrivate) + + // Remote addresses of the repo + repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL + remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost + + // Different sessions, so we can test different permissions. + // We leave Reponame blank because we don't actually then later add it according to each case if needed + // + // NB: these usernames need to match appropriate entries in models/fixtures/user.yml + writerCtx := NewAPITestContext(t, "user5", "", auth_model.AccessTokenScopeWriteRepository) + readerCtx := NewAPITestContext(t, "user4", "", auth_model.AccessTokenScopeReadRepository) + outsiderCtx := NewAPITestContext(t, "user8", "", auth_model.AccessTokenScopeReadRepository) // a user with no specific access + // Note: there's also full anonymous access, which is only available for public HTTP repos; + // it should behave the same as 'outsider' but we (will) test it separately below anyway + + // set up collaborators + doAPIAddCollaborator(ownerCtx, readerCtx.Username, perm.AccessModeRead)(t) + doAPIAddCollaborator(ownerCtx, writerCtx.Username, perm.AccessModeWrite)(t) + + // tests + t.Run("Owner", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - withAnnexCtxKeyFile(t, writerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) }) }) }) - }) - t.Run("Reader", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("SSH", func(t *testing.T) { + t.Run("Writer", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - withAnnexCtxKeyFile(t, readerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) }) }) }) - }) - t.Run("Outsider", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("SSH", func(t *testing.T) { + t.Run("Reader", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - withAnnexCtxKeyFile(t, outsiderCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath), "annex init should fail due to permissions") + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath), "annex copy --from should fail due to permissions") - }) + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "annex copy --to should fail due to permissions") + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) }) }) }) - }) - t.Run("Delete", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Outsider", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - // Delete the repo, make sure it's fully gone - doAPIDeleteRepository(ownerCtx)(t) - _, statErr := os.Stat(remoteRepoPath) - require.True(t, os.IsNotExist(statErr), "Remote annex repo should be removed from disk") + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxKeyFile(t, outsiderCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath), "annex init should fail due to permissions") + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath), "annex copy --from should fail due to permissions") + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "annex copy --to should fail due to permissions") + }) + }) + }) + }) + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Delete the repo, make sure it's fully gone + doAPIDeleteRepository(ownerCtx)(t) + _, statErr := os.Stat(remoteRepoPath) + require.True(t, os.IsNotExist(statErr), "Remote annex repo should be removed from disk") + }) }) }) }) From ee7f8249d11a65509989022f7000ba3d5bfd1d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Tue, 30 Apr 2024 12:12:39 +0200 Subject: [PATCH 015/115] Adapt patch to upstream changes A dead code check started to complain because FileCmp was only used in tests. Moved the function to test_utils. --- modules/util/filecmp.go | 87 ----------------------------- tests/integration/git_annex_test.go | 4 +- tests/test_utils.go | 79 ++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 89 deletions(-) delete mode 100644 modules/util/filecmp.go diff --git a/modules/util/filecmp.go b/modules/util/filecmp.go deleted file mode 100644 index 76e7705cc1..0000000000 --- a/modules/util/filecmp.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package util - -import ( - "bytes" - "io" - "os" -) - -// Decide if two files have the same contents or not. -// chunkSize is the size of the blocks to scan by; pass 0 to get a sensible default. -// *Follows* symlinks. -// -// May return an error if something else goes wrong; in this case, you should ignore the value of 'same'. -// -// derived from https://stackoverflow.com/a/30038571 -// under CC-BY-SA-4.0 by several contributors -func FileCmp(file1, file2 string, chunkSize int) (same bool, err error) { - if chunkSize == 0 { - chunkSize = 4 * 1024 - } - - // shortcuts: check file metadata - stat1, err := os.Stat(file1) - if err != nil { - return false, err - } - - stat2, err := os.Stat(file2) - if err != nil { - return false, err - } - - // are inputs are literally the same file? - if os.SameFile(stat1, stat2) { - return true, nil - } - - // do inputs at least have the same size? - if stat1.Size() != stat2.Size() { - return false, nil - } - - // long way: compare contents - f1, err := os.Open(file1) - if err != nil { - return false, err - } - defer f1.Close() - - f2, err := os.Open(file2) - if err != nil { - return false, err - } - defer f2.Close() - - b1 := make([]byte, chunkSize) - b2 := make([]byte, chunkSize) - for { - n1, err1 := io.ReadFull(f1, b1) - n2, err2 := io.ReadFull(f2, b2) - - // https://pkg.go.dev/io#Reader - // > Callers should always process the n > 0 bytes returned - // > before considering the error err. Doing so correctly - // > handles I/O errors that happen after reading some bytes - // > and also both of the allowed EOF behaviors. - - if !bytes.Equal(b1[:n1], b2[:n2]) { - return false, nil - } - - if (err1 == io.EOF && err2 == io.EOF) || (err1 == io.ErrUnexpectedEOF && err2 == io.ErrUnexpectedEOF) { - return true, nil - } - - // some other error, like a dropped network connection or a bad transfer - if err1 != nil { - return false, err1 - } - if err2 != nil { - return false, err2 - } - } -} diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 4a2f255737..0791e287ee 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -522,7 +522,7 @@ func doAnnexDownloadTest(remoteRepoPath, repoPath string) (err error) { return err } - match, err := util.FileCmp(localObjectPath, remoteObjectPath, 0) + match, err := tests.FileCmp(localObjectPath, remoteObjectPath, 0) if err != nil { return err } @@ -575,7 +575,7 @@ func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { return err } - match, err := util.FileCmp(localObjectPath, remoteObjectPath, 0) + match, err := tests.FileCmp(localObjectPath, remoteObjectPath, 0) if err != nil { return err } diff --git a/tests/test_utils.go b/tests/test_utils.go index e6aa9c7963..ed795facf8 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -5,9 +5,11 @@ package tests import ( + "bytes" "context" "database/sql" "fmt" + "io" "os" "path" "path/filepath" @@ -483,3 +485,80 @@ func CreateDeclarativeRepo(t *testing.T, owner *user_model.User, name string, en return CreateDeclarativeRepoWithOptions(t, owner, opts) } + +// Decide if two files have the same contents or not. +// chunkSize is the size of the blocks to scan by; pass 0 to get a sensible default. +// *Follows* symlinks. +// +// May return an error if something else goes wrong; in this case, you should ignore the value of 'same'. +// +// derived from https://stackoverflow.com/a/30038571 +// under CC-BY-SA-4.0 by several contributors +func FileCmp(file1, file2 string, chunkSize int) (same bool, err error) { + if chunkSize == 0 { + chunkSize = 4 * 1024 + } + + // shortcuts: check file metadata + stat1, err := os.Stat(file1) + if err != nil { + return false, err + } + + stat2, err := os.Stat(file2) + if err != nil { + return false, err + } + + // are inputs are literally the same file? + if os.SameFile(stat1, stat2) { + return true, nil + } + + // do inputs at least have the same size? + if stat1.Size() != stat2.Size() { + return false, nil + } + + // long way: compare contents + f1, err := os.Open(file1) + if err != nil { + return false, err + } + defer f1.Close() + + f2, err := os.Open(file2) + if err != nil { + return false, err + } + defer f2.Close() + + b1 := make([]byte, chunkSize) + b2 := make([]byte, chunkSize) + for { + n1, err1 := io.ReadFull(f1, b1) + n2, err2 := io.ReadFull(f2, b2) + + // https://pkg.go.dev/io#Reader + // > Callers should always process the n > 0 bytes returned + // > before considering the error err. Doing so correctly + // > handles I/O errors that happen after reading some bytes + // > and also both of the allowed EOF behaviors. + + if !bytes.Equal(b1[:n1], b2[:n2]) { + return false, nil + } + + if (err1 == io.EOF && err2 == io.EOF) || (err1 == io.ErrUnexpectedEOF && err2 == io.ErrUnexpectedEOF) { + return true, nil + } + + // some other error, like a dropped network connection or a bad transfer + if err1 != nil { + return false, err1 + } + if err2 != nil { + return false, err2 + } + } +} From aa1b739eada11deadda74dacd5c6ba4c6941ca8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Mon, 12 Aug 2024 11:34:45 +0200 Subject: [PATCH 016/115] Adapt patch to upstream changes --- tests/integration/git_annex_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 0791e287ee..8ebd834da0 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -752,11 +752,11 @@ func withAnnexCtxKeyFile(t *testing.T, ctx APITestContext, callback func()) { defer func() { // reset if gitAnnexUseGitSSHExists { - os.Setenv("GIT_ANNEX_USE_GIT_SSH", _gitAnnexUseGitSSH) + t.Setenv("GIT_ANNEX_USE_GIT_SSH", _gitAnnexUseGitSSH) } }() - os.Setenv("GIT_ANNEX_USE_GIT_SSH", "1") // withKeyFile works by setting GIT_SSH_COMMAND, but git-annex only respects that if this is set + t.Setenv("GIT_ANNEX_USE_GIT_SSH", "1") // withKeyFile works by setting GIT_SSH_COMMAND, but git-annex only respects that if this is set withCtxKeyFile(t, ctx, callback) } From ed712c00ba8dc81de89cd86b546023bfc6e42b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Thu, 18 Apr 2024 17:25:32 +0200 Subject: [PATCH 017/115] Install git-annex in the testing workflow --- .forgejo/workflows/testing.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 9ba8c47a01..ec23afe1f2 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -196,7 +196,7 @@ jobs: - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from with: - packages: git git-lfs + packages: git git-annex git-lfs - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-mysql-migration test-mysql' @@ -232,7 +232,7 @@ jobs: - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from with: - packages: git git-lfs + packages: git git-annex git-lfs - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-pgsql-migration test-pgsql' @@ -253,7 +253,7 @@ jobs: - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from with: - packages: git git-lfs + packages: git git-annex git-lfs - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-sqlite-migration test-sqlite' From 9482ff6dd123666998ea28d4ce1e838d58be6097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Thu, 25 Apr 2024 10:21:08 +0200 Subject: [PATCH 018/115] Fix failing tests Multiple tests that worked fine on v1.20.4-1 started to fail after the rebase onto v1.20.5-1. These tests are: - TestGitAnnexPermissions/Private/Owner/HTTP/Init - TestGitAnnexPermissions/Private/Owner/HTTP/Download - TestGitAnnexPermissions/Private/Writer/HTTP/Init - TestGitAnnexPermissions/Private/Writer/HTTP/Download - TestGitAnnexPermissions/Private/Reader/HTTP/Init - TestGitAnnexPermissions/Private/Reader/HTTP/Download What these tests have in common is that they all operate on a private repository via http with authentication. They broke at some point between v1.20.4-1 and v1.20.5-1, so I did a bisect between these two points running the offending tests. This brought me to the conclusion that ee48c0d5ea8f148c7f4a792b590d93eb51cbf67d introduced the issue. The thing is, this commit does not change any code, it only changes the test environment. Among other things that didn't look as suspicious, it changes the container image from a bespoke test_env image based on debian bullseye to a node image based on debian bookworm. Obviously, this means that there are many version differences between the two. The first one I looked at was git. The previous bullseye image used a manually installed git version 2.40.0, while the bookworm image has 2.39.2 installed. Updating git in the new image did not fix the issue, however. The next thing I looked at was the git-annex version. Bullseye had 8.20210223 installed and worked, while bookworm used 10.20230126 when the tests broke. So I tried my luck upgrading to a more recent version via neurodebian (10.20240227-1~ndall+1). This still worked fine on bullseye and now also works fine on bookworm. I have no idea why this specific version of git-annex broke the tests, but at least there was a commit to pinpoint this to, which isn't always the case with docker images silently changing beneath you... Below are the versions as they are reported by git and git-annex: bullseye (works): git version 2.30.2 git-annex version: 8.20210223 build flags: Assistant Webapp Pairing Inotify DBus DesktopNotify TorrentParser MagicMime Feeds Testsuite S3 WebDAV dependency versions: aws-0.22 bloomfilter-2.0.1.0 cryptonite-0.26 DAV-1.3.4 feed-1.3.0.1 ghc-8.8.4 http-client-0.6.4.1 persistent-sqlite-2.10.6.2 torrent-10000.1.1 uuid-1.3.13 yesod-1.6.1.0 key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X* remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external operating system: linux x86_64 supported repository versions: 8 upgrade supported from repository versions: 0 1 2 3 4 5 6 7 bullseye + git-annex from neurodebian (works): git version 2.30.2 git-annex version: 10.20240227-1~ndall+1 build flags: Assistant Webapp Pairing Inotify DBus DesktopNotify TorrentParser MagicMime Benchmark Feeds Testsuite S3 WebDAV dependency versions: aws-0.22.1 bloomfilter-2.0.1.0 cryptonite-0.29 DAV-1.3.4 feed-1.3.2.1 ghc-9.0.2 http-client-0.7.13.1 persistent-sqlite-2.13.1.0 torrent-10000.1.1 uuid-1.3.15 yesod-1.6.2.1 key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X* remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external operating system: linux x86_64 supported repository versions: 8 9 10 upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10 bookworm (fails): git version 2.39.2 git-annex version: 10.20230126 build flags: Assistant Webapp Pairing Inotify DBus DesktopNotify TorrentParser MagicMime Benchmark Feeds Testsuite S3 WebDAV dependency versions: aws-0.22.1 bloomfilter-2.0.1.0 cryptonite-0.29 DAV-1.3.4 feed-1.3.2.1 ghc-9.0.2 http-client-0.7.13.1 persistent-sqlite-2.13.1.0 torrent-10000.1.1 uuid-1.3.15 yesod-1.6.2.1 key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X* remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external operating system: linux x86_64 supported repository versions: 8 9 10 upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10 bookworm + git-annex from neurodebian (works): git version 2.39.2 git-annex version: 10.20240227-1~ndall+1 build flags: Assistant Webapp Pairing Inotify DBus DesktopNotify TorrentParser MagicMime Benchmark Feeds Testsuite S3 WebDAV dependency versions: aws-0.22.1 bloomfilter-2.0.1.0 cryptonite-0.29 DAV-1.3.4 feed-1.3.2.1 ghc-9.0.2 http-client-0.7.13.1 persistent-sqlite-2.13.1.0 torrent-10000.1.1 uuid-1.3.15 yesod-1.6.2.1 key/value backends: SHA256E SHA256 SHA512E SHA512 SHA224E SHA224 SHA384E SHA384 SHA3_256E SHA3_256 SHA3_512E SHA3_512 SHA3_224E SHA3_224 SHA3_384E SHA3_384 SKEIN256E SKEIN256 SKEIN512E SKEIN512 BLAKE2B256E BLAKE2B256 BLAKE2B512E BLAKE2B512 BLAKE2B160E BLAKE2B160 BLAKE2B224E BLAKE2B224 BLAKE2B384E BLAKE2B384 BLAKE2BP512E BLAKE2BP512 BLAKE2S256E BLAKE2S256 BLAKE2S160E BLAKE2S160 BLAKE2S224E BLAKE2S224 BLAKE2SP256E BLAKE2SP256 BLAKE2SP224E BLAKE2SP224 SHA1E SHA1 MD5E MD5 WORM URL X* remote types: git gcrypt p2p S3 bup directory rsync web bittorrent webdav adb tahoe glacier ddar git-lfs httpalso borg hook external operating system: linux x86_64 supported repository versions: 8 9 10 upgrade supported from repository versions: 0 1 2 3 4 5 6 7 8 9 10 --- .forgejo/workflows-composite/apt-install-from/action.yaml | 3 +++ .forgejo/workflows/testing.yml | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows-composite/apt-install-from/action.yaml b/.forgejo/workflows-composite/apt-install-from/action.yaml index 615e7cb184..ab55883a11 100644 --- a/.forgejo/workflows-composite/apt-install-from/action.yaml +++ b/.forgejo/workflows-composite/apt-install-from/action.yaml @@ -13,6 +13,8 @@ runs: run: | export DEBIAN_FRONTEND=noninteractive echo "deb http://deb.debian.org/debian/ ${RELEASE} main" > "/etc/apt/sources.list.d/${RELEASE}.list" + wget -O- http://neuro.debian.net/lists/bookworm.de-fzj.libre | tee /etc/apt/sources.list.d/neurodebian.sources.list + apt-key adv --recv-keys --keyserver hkps://keyserver.ubuntu.com 0xA5D32F012649A5A9 env: RELEASE: ${{inputs.release}} - name: install packages @@ -24,6 +26,7 @@ runs: - name: remove temporary package list to prevent using it in other steps run: | rm "/etc/apt/sources.list.d/${RELEASE}.list" + rm "/etc/apt/sources.list.d/neurodebian.sources.list" apt-get update -qq env: RELEASE: ${{inputs.release}} diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index ec23afe1f2..7e3a951872 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -196,7 +196,7 @@ jobs: - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from with: - packages: git git-annex git-lfs + packages: git git-annex-standalone git-lfs - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-mysql-migration test-mysql' @@ -232,7 +232,7 @@ jobs: - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from with: - packages: git git-annex git-lfs + packages: git git-annex-standalone git-lfs - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-pgsql-migration test-pgsql' @@ -253,7 +253,7 @@ jobs: - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from with: - packages: git git-annex git-lfs + packages: git git-annex-standalone git-lfs - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-sqlite-migration test-sqlite' From 90d53d48a5f8167bbcadedfecbea2071063adb94 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 19 Sep 2022 17:22:42 -0400 Subject: [PATCH 019/115] git-annex: add configuration setting [annex].ENABLED Fixes https://github.com/neuropoly/gitea/issues/8 Co-authored-by: Mathieu Guay-Paquet --- cmd/serv.go | 3 +-- cmd/web.go | 4 ++++ custom/conf/app.example.ini | 9 +++++++++ modules/setting/annex.go | 20 ++++++++++++++++++++ modules/setting/setting.go | 1 + routers/private/serv.go | 2 +- tests/integration/git_annex_test.go | 9 +++------ tests/mysql.ini.tmpl | 3 +++ tests/pgsql.ini.tmpl | 3 +++ tests/sqlite.ini.tmpl | 3 +++ 10 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 modules/setting/annex.go diff --git a/cmd/serv.go b/cmd/serv.go index 59379a91b4..62e03dfd3b 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -215,8 +215,7 @@ func runServ(c *cli.Context) error { } if verb == gitAnnexShellVerb { - // if !setting.Annex.Enabled { // TODO: https://github.com/neuropoly/gitea/issues/8 - if false { + if !setting.Annex.Enabled { return fail(ctx, "Unknown git command", "git-annex request over SSH denied, git-annex support is disabled") } diff --git a/cmd/web.go b/cmd/web.go index 3e7fdee4bf..ecc53698e3 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -322,6 +322,10 @@ func listen(m http.Handler, handleRedirector bool) error { log.Info("LFS server enabled") } + if setting.Annex.Enabled { + log.Info("git-annex enabled") + } + var err error switch setting.Protocol { case setting.HTTP: diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f8fa95bbab..789178d9be 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2678,6 +2678,15 @@ LEVEL = Info ;; Limit the number of concurrent upload/download operations within a batch ;BATCH_OPERATION_CONCURRENCY = 8 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[annex] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Whether git-annex is enabled; defaults to false +;ENABLED = false + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; settings for packages, will override storage setting diff --git a/modules/setting/annex.go b/modules/setting/annex.go new file mode 100644 index 0000000000..b8616ba87d --- /dev/null +++ b/modules/setting/annex.go @@ -0,0 +1,20 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "forgejo.org/modules/log" +) + +// Annex represents the configuration for git-annex +var Annex = struct { + Enabled bool `ini:"ENABLED"` +}{} + +func loadAnnexFrom(rootCfg ConfigProvider) { + sec := rootCfg.Section("annex") + if err := sec.MapTo(&Annex); err != nil { + log.Fatal("Failed to map Annex settings: %v", err) + } +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index eb7b9e9373..7c40f6057e 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -148,6 +148,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error { loadCamoFrom(cfg) loadI18nFrom(cfg) loadGitFrom(cfg) + loadAnnexFrom(cfg) loadMirrorFrom(cfg) loadMarkupFrom(cfg) loadQuotaFrom(cfg) diff --git a/routers/private/serv.go b/routers/private/serv.go index 82953b5af9..5d3ab0ed78 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -289,7 +289,7 @@ func ServCommand(ctx *context.PrivateContext) { repo.IsPrivate || owner.Visibility.IsPrivate() || (user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey - ( /*setting.Annex.Enabled && */ len(verbs) > 0 && verbs[0] == "git-annex-shell") || // git-annex has its own permission enforcement, for which we expose results.UserMode + (setting.Annex.Enabled && len(verbs) > 0 && verbs[0] == "git-annex-shell") || // git-annex has its own permission enforcement, for which we expose results.UserMode setting.Service.RequireSignInView) { if key.Type == asymkey_model.KeyTypeDeploy { results.UserMode = deployKey.Mode diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 8ebd834da0..27d7267fb6 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -62,12 +62,9 @@ Test that permissions are enforced on git-annex-shell commands. Along the way, test that uploading, downloading, and deleting all work. */ func TestGitAnnexPermissions(t *testing.T) { - /* - // TODO: look into how LFS did this - if !setting.Annex.Enabled { - t.Skip() - } - */ + if !setting.Annex.Enabled { + t.Skip("Skipping since annex support is disabled.") + } // Each case below is split so that 'clone' is done as // the repo owner, but 'copy' as the user under test. diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl index e15e79952b..b8320265ab 100644 --- a/tests/mysql.ini.tmpl +++ b/tests/mysql.ini.tmpl @@ -97,6 +97,9 @@ DISABLE_QUERY_AUTH_TOKEN = true [lfs] PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/data/lfs +[annex] +ENABLED = true + [packages] ENABLED = true diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index 340531fb38..781508a648 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -122,6 +122,9 @@ MINIO_LOCATION = us-east-1 MINIO_USE_SSL = false MINIO_CHECKSUM_ALGORITHM = md5 +[annex] +ENABLED = true + [packages] ENABLED = true diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 277916a539..231c0d19c2 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -102,6 +102,9 @@ JWT_SECRET = KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko [lfs] PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/lfs +[annex] +ENABLED = true + [packages] ENABLED = true From 3d9d84ad70fc4000615dfbbc7c4f4d61e13d3501 Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 20 Sep 2022 16:17:56 -0400 Subject: [PATCH 020/115] git-annex: support downloading over HTTP This makes HTTP symmetric with SSH clone URLs. This gives us the fancy feature of _anonymous_ downloads, so people can access datasets without having to set up an account or manage ssh keys. Previously, to access "open access" data shared this way, users would need to: 1. Create an account on gitea.example.com 2. Create ssh keys 3. Upload ssh keys (and make sure to find and upload the correct file) 4. `git clone git@gitea.example.com:user/dataset.git` 5. `cd dataset` 6. `git annex get` This cuts that down to just the last three steps: 1. `git clone https://gitea.example.com/user/dataset.git` 2. `cd dataset` 3. `git annex get` This is significantly simpler for downstream users, especially for those unfamiliar with the command line. Unfortunately there's no uploading. While git-annex supports uploading over HTTP to S3 and some other special remotes, it seems to fail on a _plain_ HTTP remote. See https://github.com/neuropoly/gitea/issues/7 and https://git-annex.branchable.com/forum/HTTP_uploads/#comment-ce28adc128fdefe4c4c49628174d9b92. This is not a major loss since no one wants uploading to be anonymous anyway. To support private repos, I had to hunt down and patch a secret extra security corner that Gitea only applies to HTTP for some reason (services/auth/basic.go). This was guided by https://git-annex.branchable.com/tips/setup_a_public_repository_on_a_web_site/ Fixes https://github.com/neuropoly/gitea/issues/3 Co-authored-by: Mathieu Guay-Paquet --- modules/git/command.go | 3 +- routers/web/repo/githttp.go | 31 ++ routers/web/web.go | 13 + services/auth/auth.go | 11 + services/auth/basic.go | 4 +- tests/integration/git_annex_test.go | 360 +++++++++++++++++- .../git_helper_for_declarative_test.go | 7 + 7 files changed, 412 insertions(+), 17 deletions(-) diff --git a/modules/git/command.go b/modules/git/command.go index bf1d624dbf..fd29ac36e9 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -447,12 +447,13 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS } // AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests +// It also re-enables git-credential(1), which is used to test git-annex's HTTP support func AllowLFSFiltersArgs() TrustedCmdArgs { // Now here we should explicitly allow lfs filters to run filteredLFSGlobalArgs := make(TrustedCmdArgs, len(globalCommandArgs)) j := 0 for _, arg := range globalCommandArgs { - if strings.Contains(string(arg), "lfs") { + if strings.Contains(string(arg), "lfs") || strings.Contains(string(arg), "credential") { j-- } else { filteredLFSGlobalArgs[j] = arg diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 650b1d88f4..019befc303 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -597,3 +597,34 @@ func GetIdxFile(ctx *context.Context) { h.sendFile(ctx, "application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx") } } + +// GetAnnexObject implements git-annex dumb HTTP +func GetAnnexObject(ctx *context.Context) { + h := httpBase(ctx) + if h != nil { + // git-annex objects are stored in .git/annex/objects/{hash1}/{hash2}/{key}/{key} + // where key is a string containing the size and (usually SHA256) checksum of the file, + // and hash1+hash2 are the first few bits of the md5sum of key itself. + // ({hash1}/{hash2}/ is just there to avoid putting too many files in one directory) + // ref: https://git-annex.branchable.com/internals/hashing/ + + // keyDir should = key, but we don't enforce that + object := path.Join(ctx.Params("hash1"), ctx.Params("hash2"), ctx.Params("keyDir"), ctx.Params("key")) + + // Sanitize the input against directory traversals. + // + // This works because at the filesystem root, "/.." = "/"; + // So if a path starts rooted ("/"), path.Clean(), which + // path.Join() calls internally, removes all '..' prefixes. + // After, this unroots the path unconditionally ([1:]), which + // works because we know the input is never supposed to be rooted. + // + // The router code probably also disallows "..", so this + // should be redundant, but it's defensive to keep it + // whenever touching filesystem paths with user input. + object = path.Join("/", object)[1:] + + h.setHeaderCacheForever() + h.sendFile("application/octet-stream", "annex/objects/"+object) + } +} diff --git a/routers/web/web.go b/routers/web/web.go index 303167a6b9..78ab60545b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -350,6 +350,13 @@ func registerRoutes(m *web.Route) { } } + annexEnabled := func(ctx *context.Context) { + if !setting.Annex.Enabled { + ctx.Error(http.StatusNotFound) + return + } + } + federationEnabled := func(ctx *context.Context) { if !setting.Federation.Enabled { ctx.Error(http.StatusNotFound) @@ -1632,6 +1639,12 @@ func registerRoutes(m *web.Route) { }) }, ignSignInAndCsrf, lfsServerEnabled) + m.Group("", func() { + // for git-annex + m.GetOptions("/config", repo.GetTextFile("config")) // needed by clients reading annex.uuid during `git annex initremote` + m.GetOptions("/annex/objects/{hash1}/{hash2}/{keyDir}/{key}", repo.GetAnnexObject) + }, ignSignInAndCsrf, annexEnabled, context_service.UserAssignmentWeb()) + gitHTTPRouters(m) }) }) diff --git a/services/auth/auth.go b/services/auth/auth.go index 85c9296ced..9d72a88c1f 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -61,6 +61,17 @@ func isArchivePath(req *http.Request) bool { return archivePathRe.MatchString(req.URL.Path) } +var annexPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/annex/`) + +func isAnnexPath(req *http.Request) bool { + if setting.Annex.Enabled { + // "/config" is git's config, not specifically git-annex's; but the only current + // user of it is when git-annex downloads the annex.uuid during 'git annex init'. + return strings.HasSuffix(req.URL.Path, "/config") || annexPathRe.MatchString(req.URL.Path) + } + return false +} + // handleSignIn clears existing session variables and stores new ones for the specified user object func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) { // We need to regenerate the session... diff --git a/services/auth/basic.go b/services/auth/basic.go index f259ad5f69..7adcd8914a 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -43,8 +43,8 @@ func (b *Basic) Name() string { // name/token on successful validation. // Returns nil if header is empty or validation fails. func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { - // Basic authentication should only fire on API, Download or on Git or LFSPaths - if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) { + // Basic authentication should only fire on API, Download or on Git, LFSPaths or Git-Annex paths + if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) && !isAnnexPath(req) { return nil, nil } diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 27d7267fb6..cea52d813f 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -59,7 +59,8 @@ func doCreateRemoteAnnexRepository(t *testing.T, u *url.URL, ctx APITestContext, /* Test that permissions are enforced on git-annex-shell commands. - Along the way, test that uploading, downloading, and deleting all work. + Along the way, this also tests that uploading, downloading, and deleting all work, + so we haven't written separate tests for those. */ func TestGitAnnexPermissions(t *testing.T) { if !setting.Annex.Enabled { @@ -75,6 +76,16 @@ func TestGitAnnexPermissions(t *testing.T) { // 'annex copy' -- potentially leaving a security gap. onGiteaRun(t, func(t *testing.T, u *url.URL) { + // Tell git-annex to allow http://127.0.0.1, http://localhost and http://::1. Without + // this, all `git annex` commands will silently fail when run against http:// remotes + // without explaining what's wrong. + // + // Note: onGiteaRun() sets up an alternate HOME so this actually edits + // tests/integration/gitea-integration-*/data/home/.gitconfig and + // if you're debugging you need to remember to match that. + _, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "config").AddOptionValues("--global").AddArguments("annex.security.allowed-ip-addresses", "all").RunStdString(&git.RunOpts{}) + require.NoError(t, err) + forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { t.Run("Public", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -89,8 +100,6 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, err) require.False(t, repo.IsPrivate) - // Remote addresses of the repo - repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost // Different sessions, so we can test different permissions. @@ -112,6 +121,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -139,6 +150,31 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) }) t.Run("Writer", func(t *testing.T) { @@ -147,6 +183,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -174,6 +212,31 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) }) t.Run("Reader", func(t *testing.T) { @@ -182,6 +245,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -209,6 +274,31 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) }) t.Run("Outsider", func(t *testing.T) { @@ -217,6 +307,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -244,6 +336,61 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) + }) + + t.Run("Anonymous", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Only HTTP has an anonymous mode + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + // unlike the other tests, at this step we *do not* define credentials: + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) }) t.Run("Delete", func(t *testing.T) { @@ -269,8 +416,6 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, err) require.True(t, repo.IsPrivate) - // Remote addresses of the repo - repoURL := createSSHUrl(ownerCtx.GitPath(), u) // remote git URL remoteRepoPath := path.Join(setting.RepoRootPath, ownerCtx.GitPath()) // path on disk -- which can be examined directly because we're testing from localhost // Different sessions, so we can test different permissions. @@ -294,6 +439,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -321,6 +468,31 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) }) t.Run("Writer", func(t *testing.T) { @@ -329,6 +501,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -356,6 +530,31 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) }) t.Run("Reader", func(t *testing.T) { @@ -364,6 +563,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -391,6 +592,31 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) }) t.Run("Outsider", func(t *testing.T) { @@ -399,6 +625,8 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("SSH", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions @@ -426,6 +654,61 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + }) + }) + + t.Run("Anonymous", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Only HTTP has an anonymous mode + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + // unlike the other tests, at this step we *do not* define credentials: + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) }) t.Run("Delete", func(t *testing.T) { @@ -447,7 +730,7 @@ Test that 'git annex init' works. precondition: repoPath contains a pre-cloned repo set up by doInitAnnexRepository(). */ func doAnnexInitTest(remoteRepoPath, repoPath string) (err error) { - _, _, err = git.NewCommand(git.DefaultContext, "annex", "init", "cloned-repo").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "init", "cloned-repo").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { return fmt.Errorf("Couldn't `git annex init`: %w", err) } @@ -455,7 +738,7 @@ func doAnnexInitTest(remoteRepoPath, repoPath string) (err error) { // - method 0: 'git config remote.origin.annex-uuid'. // Demonstrates that 'git annex init' successfully contacted // the remote git-annex and was able to learn its ID number. - readAnnexUUID, _, err := git.NewCommand(git.DefaultContext, "config", "remote.origin.annex-uuid").RunStdString(&git.RunOpts{Dir: repoPath}) + readAnnexUUID, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "config", "remote.origin.annex-uuid").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { return fmt.Errorf("Couldn't read remote `git config remote.origin.annex-uuid`: %w", err) } @@ -466,7 +749,7 @@ func doAnnexInitTest(remoteRepoPath, repoPath string) (err error) { return fmt.Errorf("'git config remote.origin.annex-uuid' should have been able to download the remote's uuid; but instead read '%s'", readAnnexUUID) } - remoteAnnexUUID, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: remoteRepoPath}) + remoteAnnexUUID, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: remoteRepoPath}) if err != nil { return fmt.Errorf("Couldn't read local `git config annex.uuid`: %w", err) } @@ -483,7 +766,7 @@ func doAnnexInitTest(remoteRepoPath, repoPath string) (err error) { // - method 1: 'git annex whereis'. // Demonstrates that git-annex understands the annexed file can be found in the remote annex. - annexWhereis, _, err := git.NewCommand(git.DefaultContext, "annex", "whereis", "large.bin").RunStdString(&git.RunOpts{Dir: repoPath}) + annexWhereis, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "whereis", "large.bin").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { return fmt.Errorf("Couldn't `git annex whereis large.bin`: %w", err) } @@ -502,7 +785,7 @@ func doAnnexDownloadTest(remoteRepoPath, repoPath string) (err error) { // "git annex copy" will notice and run "git annex init", silently. // This shouldn't change any results, but be aware in case it does. - _, _, err = git.NewCommand(git.DefaultContext, "annex", "copy", "--from", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "copy", "--from", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { return err } @@ -550,12 +833,12 @@ func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { return err } - _, _, err = git.NewCommand(git.DefaultContext, "annex", "copy", "--to", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "copy", "--to", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { return err } - _, _, err = git.NewCommand(git.DefaultContext, "annex", "sync", "--no-content").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "sync", "--no-content").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { return err } @@ -667,7 +950,7 @@ func doInitAnnexRepository(repoPath string) error { } // 'git annex init' - err = git.NewCommand(git.DefaultContext, "annex", "init", "test-repo").Run(&git.RunOpts{Dir: repoPath}) + err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "init", "test-repo").Run(&git.RunOpts{Dir: repoPath}) if err != nil { return err } @@ -705,7 +988,7 @@ func doInitRemoteAnnexRepository(t *testing.T, repoURL *url.URL) error { return err } - _, _, err = git.NewCommand(git.DefaultContext, "annex", "sync", "--content").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "sync", "--content").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { return err } @@ -757,3 +1040,52 @@ func withAnnexCtxKeyFile(t *testing.T, ctx APITestContext, callback func()) { withCtxKeyFile(t, ctx, callback) } + +/* +Like withKeyFile(), but sets HTTP credentials instead of SSH credentials. + + It does this by temporarily arranging through `git config --global` + to use git-credential-store(1) with the password written to a tempfile. + + This is the only reliable way to pass HTTP credentials non-interactively + to git-annex. See https://git-annex.branchable.com/bugs/http_remotes_ignore_annex.web-options_--netrc/#comment-b5a299e9826b322f2d85c96d4929a430 + for joeyh's proclamation on the subject. + + This **is only effective** when used around git.NewCommandContextNoGlobals() calls. + git.NewCommand() disables credential.helper as a precaution (see modules/git/git.go). + + In contrast, the tests in git_test.go put the password in the remote's URL like + `git config remote.origin.url http://user2:password@localhost:3003/user2/repo-name.git`, + writing the password in repoPath+"/.git/config". That would be equally good, except + that git-annex ignores it! +*/ +func withAnnexCtxHTTPPassword(t *testing.T, u *url.URL, ctx APITestContext, callback func()) { + credentialedURL := *u + credentialedURL.User = url.UserPassword(ctx.Username, userPassword) // NB: all test users use the same password + + creds := path.Join(t.TempDir(), "creds") + require.NoError(t, os.WriteFile(creds, []byte(credentialedURL.String()), 0o600)) + + originalCredentialHelper, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "config").AddOptionValues("--global", "credential.helper").RunStdString(&git.RunOpts{}) + if err != nil && !err.IsExitCode(1) { + // ignore the 'error' thrown when credential.helper is unset (when git config returns 1) + // but catch all others + require.NoError(t, err) + } + hasOriginalCredentialHelper := (err == nil) + + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "config").AddOptionValues("--global", "credential.helper", fmt.Sprintf("store --file=%s", creds)).RunStdString(&git.RunOpts{}) + require.NoError(t, err) + + defer (func() { + // reset + if hasOriginalCredentialHelper { + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "config").AddOptionValues("--global").AddArguments("credential.helper").AddDynamicArguments(originalCredentialHelper).RunStdString(&git.RunOpts{}) + } else { + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "config").AddOptionValues("--global").AddOptionValues("--unset").AddArguments("credential.helper").RunStdString(&git.RunOpts{}) + } + require.NoError(t, err) + })() + + callback() +} diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 8921768c1f..7b00ebd3a1 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -73,6 +73,13 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) { callback(keyFile) } +func createHTTPUrl(gitPath string, u *url.URL) *url.URL { + // this assumes u contains the HTTP base URL that Gitea is running on + u2 := *u + u2.Path = gitPath + return &u2 +} + func createSSHUrl(gitPath string, u *url.URL) *url.URL { u2 := *u u2.Scheme = "ssh" From bb96dc35e9bf3120182a3fa40cf8541f062d2070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 1 Mar 2024 14:23:33 +0100 Subject: [PATCH 021/115] Replace m.GetOptions with m.Methods This applies the same changes that were done in 265cd70bdb152291a13e520cff1da70b8c029432 to the git-annex specific routes as well. --- routers/web/web.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/web/web.go b/routers/web/web.go index 78ab60545b..eb17259864 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1641,8 +1641,8 @@ func registerRoutes(m *web.Route) { m.Group("", func() { // for git-annex - m.GetOptions("/config", repo.GetTextFile("config")) // needed by clients reading annex.uuid during `git annex initremote` - m.GetOptions("/annex/objects/{hash1}/{hash2}/{keyDir}/{key}", repo.GetAnnexObject) + m.Methods("GET,OPTIONS", "/config", repo.GetTextFile("config")) // needed by clients reading annex.uuid during `git annex initremote` + m.Methods("GET,OPTIONS", "/annex/objects/{hash1}/{hash2}/{keyDir}/{key}", repo.GetAnnexObject) }, ignSignInAndCsrf, annexEnabled, context_service.UserAssignmentWeb()) gitHTTPRouters(m) From e4c5a6449946acb939fcb22662e749b9b7533f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Mon, 29 Apr 2024 15:52:47 +0200 Subject: [PATCH 022/115] Fix exit code check for git command The err.IsExitCode method was changed to a function IsErrorExitCode taking err as its first argument in 1e7a6483b8322ad5e1183545a6283f137a0546ac. --- tests/integration/git_annex_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index cea52d813f..3eff7c7062 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -1067,7 +1067,7 @@ func withAnnexCtxHTTPPassword(t *testing.T, u *url.URL, ctx APITestContext, call require.NoError(t, os.WriteFile(creds, []byte(credentialedURL.String()), 0o600)) originalCredentialHelper, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "config").AddOptionValues("--global", "credential.helper").RunStdString(&git.RunOpts{}) - if err != nil && !err.IsExitCode(1) { + if err != nil && !git.IsErrorExitCode(err, 1) { // ignore the 'error' thrown when credential.helper is unset (when git config returns 1) // but catch all others require.NoError(t, err) From 5288b230d2020a215a64b07dae76faf005b82283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Mon, 29 Apr 2024 17:44:58 +0200 Subject: [PATCH 023/115] Adapt patch to upstream changes Usage of `path` was replaced by `path/filepath` in upstream forgejo, and it made sense to use that as well where `path` was previously used. The `setHeaderCacheForever` function and the `sendFile` method had their signature changed. --- routers/web/repo/githttp.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 019befc303..3c95f4bd4c 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -609,7 +609,7 @@ func GetAnnexObject(ctx *context.Context) { // ref: https://git-annex.branchable.com/internals/hashing/ // keyDir should = key, but we don't enforce that - object := path.Join(ctx.Params("hash1"), ctx.Params("hash2"), ctx.Params("keyDir"), ctx.Params("key")) + object := filepath.Join(ctx.Params("hash1"), ctx.Params("hash2"), ctx.Params("keyDir"), ctx.Params("key")) // Sanitize the input against directory traversals. // @@ -622,9 +622,9 @@ func GetAnnexObject(ctx *context.Context) { // The router code probably also disallows "..", so this // should be redundant, but it's defensive to keep it // whenever touching filesystem paths with user input. - object = path.Join("/", object)[1:] + object = filepath.Join(string(filepath.Separator), object)[1:] - h.setHeaderCacheForever() - h.sendFile("application/octet-stream", "annex/objects/"+object) + setHeaderCacheForever(ctx) + h.sendFile(ctx, "application/octet-stream", "annex/objects/"+object) } } From 75afe98f5c6344da56b494772d75290bc90c1d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Tue, 30 Apr 2024 11:25:16 +0200 Subject: [PATCH 024/115] Adapt patch to upstream changes The "context_service" import was changed to use the default name of just "context". The patch set had to be adapted for that. --- routers/web/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/web.go b/routers/web/web.go index eb17259864..91400d5d17 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1643,7 +1643,7 @@ func registerRoutes(m *web.Route) { // for git-annex m.Methods("GET,OPTIONS", "/config", repo.GetTextFile("config")) // needed by clients reading annex.uuid during `git annex initremote` m.Methods("GET,OPTIONS", "/annex/objects/{hash1}/{hash2}/{keyDir}/{key}", repo.GetAnnexObject) - }, ignSignInAndCsrf, annexEnabled, context_service.UserAssignmentWeb()) + }, ignSignInAndCsrf, annexEnabled, context.UserAssignmentWeb()) gitHTTPRouters(m) }) From 65ed342bf973a33cad9125647c1afd007cc2f67f Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 27 Nov 2022 00:28:55 -0500 Subject: [PATCH 025/115] git-annex: create modules/annex This moves the `annexObjectPath()` helper out of the tests and into a dedicated sub-package as `annex.ContentLocation()`, and expands it with `.Pointer()` (which validates using `git annex examinekey`), `.IsAnnexed()` and `.Content()` to make it a more useful module. The tests retain their own wrapper version of `ContentLocation()` because I tried to follow close to the API modules/lfs uses, which in terms of abstract `git.Blob` and `git.TreeEntry` objects, not in terms of `repoPath string`s which are more convenient for the tests. --- modules/annex/annex.go | 154 ++++++++++++++++++++++++++++ modules/git/blob.go | 4 + tests/integration/git_annex_test.go | 41 ++++---- 3 files changed, 181 insertions(+), 18 deletions(-) create mode 100644 modules/annex/annex.go diff --git a/modules/annex/annex.go b/modules/annex/annex.go new file mode 100644 index 0000000000..96b604693e --- /dev/null +++ b/modules/annex/annex.go @@ -0,0 +1,154 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +// Unlike modules/lfs, which operates mainly on git.Blobs, this operates on git.TreeEntrys. +// The motivation for this is that TreeEntrys have an easy pointer to the on-disk repo path, +// while blobs do not (in fact, if building with TAGS=gogit, blobs might exist only in a mock +// filesystem, living only in process RAM). We must have the on-disk path to do anything +// useful with git-annex because all of its interesting data is on-disk under .git/annex/. + +package annex + +import ( + "errors" + "fmt" + "os" + "path" + "strings" + + "forgejo.org/modules/git" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" +) + +const ( + // > The maximum size of a pointer file is 32 kb. + // - https://git-annex.branchable.com/internals/pointer_file/ + // It's unclear if that's kilobytes or kibibytes; assuming kibibytes: + blobSizeCutoff = 32 * 1024 +) + +// ErrInvalidPointer occurs if the pointer's value doesn't parse +var ErrInvalidPointer = errors.New("Not a git-annex pointer") + +// Gets the content of the blob as raw text, up to n bytes. +// (the pre-existing blob.GetBlobContent() has a hardcoded 1024-byte limit) +func getBlobContent(b *git.Blob, n int) (string, error) { + dataRc, err := b.DataAsync() + if err != nil { + return "", err + } + defer dataRc.Close() + buf := make([]byte, n) + n, _ = util.ReadAtMost(dataRc, buf) + buf = buf[:n] + return string(buf), nil +} + +func Pointer(blob *git.Blob) (string, error) { + // git-annex doesn't seem fully spec what its pointer are, but + // the fullest description is here: + // https://git-annex.branchable.com/internals/pointer_file/ + + // a pointer can be: + // the original format, generated by `git annex add`: a symlink to '.git/annex/objects/$HASHDIR/$HASHDIR2/$KEY/$KEY' + // the newer, git-lfs influenced, format, generated by `git annex smudge`: a text file containing '/annex/objects/$KEY' + // + // in either case we can extract the $KEY the same way, and we need not actually know if it's a symlink or not because + // git.Blob.DataAsync() works like open() + readlink(), handling both cases in one. + + if blob.Size() > blobSizeCutoff { + // > The maximum size of a pointer file is 32 kb. If it is any longer, it is not considered to be a valid pointer file. + // https://git-annex.branchable.com/internals/pointer_file/ + + // It's unclear to me whether the same size limit applies to symlink-pointers, but it seems sensible to limit them too. + return "", ErrInvalidPointer + } + + pointer, err := getBlobContent(blob, blobSizeCutoff) + if err != nil { + return "", fmt.Errorf("error reading %s: %w", blob.Name(), err) + } + + // the spec says a pointer file can contain multiple lines each with a pointer in them + // but that makes no sense to me, so I'm just ignoring all but the first + lines := strings.Split(pointer, "\n") + if len(lines) < 1 { + return "", ErrInvalidPointer + } + pointer = lines[0] + + // in both the symlink and pointer-file formats, the pointer must have "/annex/" somewhere in it + if !strings.Contains(pointer, "/annex/") { + return "", ErrInvalidPointer + } + + // extract $KEY + pointer = path.Base(strings.TrimSpace(pointer)) + + // ask git-annex's opinion on $KEY + // XXX: this is probably a bit slow, especially if this operation gets run often + // and examinekey is not that strict: + // - it doesn't enforce that the "BACKEND" tag is one it knows, + // - it doesn't enforce that the fields and their format fit the "BACKEND" tag + // so maybe this is a wasteful step + _, examineStderr, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "examinekey").AddDynamicArguments(pointer).RunStdString(&git.RunOpts{Dir: blob.Repo().Path}) + if err != nil { + // TODO: make ErrInvalidPointer into a type capable of wrapping err + if strings.TrimSpace(examineStderr) == "git-annex: bad key" { + return "", ErrInvalidPointer + } + return "", err + } + + return pointer, nil +} + +// return the absolute path of the content pointed to by the annex pointer stored in the git object +// errors if the content is not found in this repo +func ContentLocation(blob *git.Blob) (string, error) { + pointer, err := Pointer(blob) + if err != nil { + return "", err + } + + contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(pointer).RunStdString(&git.RunOpts{Dir: blob.Repo().Path}) + if err != nil { + return "", fmt.Errorf("in %s: %s does not seem to be a valid annexed file: %w", blob.Repo().Path, pointer, err) + } + contentLocation = strings.TrimSpace(contentLocation) + contentLocation = path.Clean("/" + contentLocation)[1:] // prevent directory traversals + contentLocation = path.Join(blob.Repo().Path, contentLocation) + + return contentLocation, nil +} + +// returns a stream open to the annex content +func Content(blob *git.Blob) (*os.File, error) { + contentLocation, err := ContentLocation(blob) + if err != nil { + return nil, err + } + + return os.Open(contentLocation) +} + +// whether the object appears to be a valid annex pointer +// does *not* verify if the content is actually in this repo; +// for that, use ContentLocation() +func IsAnnexed(blob *git.Blob) (bool, error) { + if !setting.Annex.Enabled { + return false, nil + } + + // Pointer() is written to only return well-formed pointers + // so the test is just to see if it errors + _, err := Pointer(blob) + if err != nil { + if errors.Is(err, ErrInvalidPointer) { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/modules/git/blob.go b/modules/git/blob.go index 3fda358938..8f912189ed 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -126,6 +126,10 @@ func (b *blobReader) Close() error { return nil } +func (b *Blob) Repo() *Repository { + return b.repo +} + // Name returns name of the tree entry this blob object was created from (or empty string) func (b *Blob) Name() string { return b.name diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 3eff7c7062..a583e41df2 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -19,6 +19,7 @@ import ( "forgejo.org/models/db" "forgejo.org/models/perm" repo_model "forgejo.org/models/repo" + "forgejo.org/modules/annex" "forgejo.org/modules/git" "forgejo.org/modules/setting" api "forgejo.org/modules/structs" @@ -791,13 +792,13 @@ func doAnnexDownloadTest(remoteRepoPath, repoPath string) (err error) { } // verify the file was downloaded - localObjectPath, err := annexObjectPath(repoPath, "large.bin") + localObjectPath, err := contentLocation(repoPath, "large.bin") if err != nil { return err } // localObjectPath := path.Join(repoPath, "large.bin") // or, just compare against the checked-out file - remoteObjectPath, err := annexObjectPath(remoteRepoPath, "large.bin") + remoteObjectPath, err := contentLocation(remoteRepoPath, "large.bin") if err != nil { return err } @@ -844,13 +845,13 @@ func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { } // verify the file was uploaded - localObjectPath, err := annexObjectPath(repoPath, "contribution.bin") + localObjectPath, err := contentLocation(repoPath, "contribution.bin") if err != nil { return err } // localObjectPath := path.Join(repoPath, "contribution.bin") // or, just compare against the checked-out file - remoteObjectPath, err := annexObjectPath(remoteRepoPath, "contribution.bin") + remoteObjectPath, err := contentLocation(remoteRepoPath, "contribution.bin") if err != nil { return err } @@ -1004,26 +1005,30 @@ Find the path in .git/annex/objects/ of the contents for a given annexed file. TODO: pass a parameter to allow examining non-HEAD branches */ -func annexObjectPath(repoPath, file string) (string, error) { - // NB: `git annex lookupkey` is more reliable, but doesn't work in bare repos. - annexKey, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "show").AddDynamicArguments("HEAD:" + file).RunStdString(&git.RunOpts{Dir: repoPath}) +func contentLocation(repoPath, file string) (path string, err error) { + path = "" + + repo, err := git.OpenRepository(git.DefaultContext, repoPath) if err != nil { - return "", fmt.Errorf("in %s: %w", repoPath, err) // the error from git prints the filename but not repo + return path, nil } - // There are two formats an annexed file pointer might be: - // * a symlink to .git/annex/objects/$HASHDIR/$ANNEX_KEY/$ANNEX_KEY - used by files created with 'git annex add' - // * a text file containing /annex/objects/$ANNEX_KEY - used by files for which 'git add' was configured to run git-annex-smudge - // This recovers $ANNEX_KEY from either case: - annexKey = path.Base(strings.TrimSpace(annexKey)) - - contentPath, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(annexKey).RunStdString(&git.RunOpts{Dir: repoPath}) + commitID, err := repo.GetRefCommitID("HEAD") // NB: to examine a *branch*, prefix with "refs/branch/", or call repo.GetBranchCommitID(); ditto for tags if err != nil { - return "", fmt.Errorf("in %s: %s does not seem to be annexed: %w", repoPath, file, err) + return path, nil } - contentPath = strings.TrimSpace(contentPath) - return path.Join(repoPath, contentPath), nil + commit, err := repo.GetCommit(commitID) + if err != nil { + return path, nil + } + + treeEntry, err := commit.GetTreeEntryByPath(file) + if err != nil { + return path, nil + } + + return annex.ContentLocation(treeEntry.Blob()) } /* like withKeyFile(), but automatically sets it the account given in ctx for use by git-annex */ From 98e5f6612259bc7c90954948659ee9d6410e29f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Tue, 14 May 2024 11:48:43 +0200 Subject: [PATCH 026/115] Adapt patch to upstream changes The git repository must be closed after using it. Without this change some tests started to fail due to the lingering repository running into a timeout. --- tests/integration/git_annex_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index a583e41df2..4deb6ba7e9 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -1012,6 +1012,7 @@ func contentLocation(repoPath, file string) (path string, err error) { if err != nil { return path, nil } + defer repo.Close() commitID, err := repo.GetRefCommitID("HEAD") // NB: to examine a *branch*, prefix with "refs/branch/", or call repo.GetBranchCommitID(); ditto for tags if err != nil { From f33d0976c66e8eb264f0a105eb0305762cc6b30c Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 27 Nov 2022 00:40:06 -0500 Subject: [PATCH 027/115] git-annex: make /media/ download annexed content Previously, Gitea's LFS support allowed direct-downloads of LFS content, via http://$HOSTNAME:$PORT/$USER/$REPO/media/branch/$BRANCH/$FILE Expand that grace to git-annex too. Now /media should provide the relevant *content* from the .git/annex/objects/ folder. This adds tests too. And expands the tests to try symlink-based annexing, since /media implicitly supports both that and pointer-file-based annexing. --- routers/web/repo/download.go | 21 ++++ tests/integration/git_annex_test.go | 146 ++++++++++++++++++++++++---- 2 files changed, 148 insertions(+), 19 deletions(-) diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go index fc82ece4cb..50ad85735b 100644 --- a/routers/web/repo/download.go +++ b/routers/web/repo/download.go @@ -8,6 +8,7 @@ import ( "time" git_model "forgejo.org/models/git" + "forgejo.org/modules/annex" "forgejo.org/modules/git" "forgejo.org/modules/httpcache" "forgejo.org/modules/lfs" @@ -78,6 +79,26 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Tim } closed = true + // check for git-annex files + // (this code is weirdly redundant because I'm trying not to delete any lines in order to make merges easier) + isAnnexed, err := annex.IsAnnexed(blob) + if err != nil { + ctx.ServerError("annex.IsAnnexed", err) + return err + } + if isAnnexed { + content, err := annex.Content(blob) + if err != nil { + // XXX are there any other possible failure cases here? + // there are, there could be unrelated io errors; those should be ctx.ServerError()s + ctx.NotFound("annex.Content", err) + return err + } + defer content.Close() + common.ServeContentByReadSeeker(ctx.Base, ctx.Repo.TreePath, lastModified, content) + return nil + } + return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) } diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 4deb6ba7e9..df933add3a 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -7,7 +7,9 @@ package integration import ( "errors" "fmt" + "io" "math/rand" + "net/http" "net/url" "os" "path" @@ -57,6 +59,63 @@ func doCreateRemoteAnnexRepository(t *testing.T, u *url.URL, ctx APITestContext, return nil } +func TestGitAnnexMedia(t *testing.T) { + if !setting.Annex.Enabled { + t.Skip("Skipping since annex support is disabled.") + } + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx := NewAPITestContext(t, "user2", "annex-media-test", auth_model.AccessTokenScopeWriteRepository) + + // create a public repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false)) + + // the filenames here correspond to specific cases defined in doInitAnnexRepository() + t.Run("AnnexSymlink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doAnnexMediaTest(t, ctx, "annexed.tiff") + }) + t.Run("AnnexPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doAnnexMediaTest(t, ctx, "annexed.bin") + }) + }) +} + +func doAnnexMediaTest(t *testing.T, ctx APITestContext, file string) { + // Make sure that downloading via /media on the website recognizes it should give the annexed content + + // TODO: + // - [ ] roll this into TestGitAnnexPermissions to ensure that permission enforcement works correctly even on /media? + + session := loginUser(t, ctx.Username) // logs in to the http:// site/API, storing a cookie; + // this is a different auth method than the git+ssh:// or git+http:// protocols TestGitAnnexPermissions uses! + + // compute server-side path of the annexed file + remoteRepoPath := path.Join(setting.RepoRootPath, ctx.GitPath()) + remoteObjectPath, err := contentLocation(remoteRepoPath, file) + require.NoError(t, err) + + // download annexed file + localObjectPath := path.Join(t.TempDir(), file) + fd, err := os.OpenFile(localObjectPath, os.O_CREATE|os.O_WRONLY, 0o777) + defer fd.Close() + require.NoError(t, err) + + mediaLink := path.Join("/", ctx.Username, ctx.Reponame, "/media/branch/master", file) + req := NewRequest(t, "GET", mediaLink) + resp := session.MakeRequest(t, req, http.StatusOK) + + _, err = io.Copy(fd, resp.Body) + require.NoError(t, err) + fd.Close() + + // verify the download + match, err := util.FileCmp(localObjectPath, remoteObjectPath, 0) + require.NoError(t, err) + require.True(t, match, "Annexed files should be the same") +} + /* Test that permissions are enforced on git-annex-shell commands. @@ -766,16 +825,16 @@ func doAnnexInitTest(remoteRepoPath, repoPath string) (err error) { } // - method 1: 'git annex whereis'. - // Demonstrates that git-annex understands the annexed file can be found in the remote annex. - annexWhereis, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "whereis", "large.bin").RunStdString(&git.RunOpts{Dir: repoPath}) + // Demonstrates that git-annex understands annexed files can be found in the remote annex. + annexWhereis, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "whereis", "annexed.bin").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { - return fmt.Errorf("Couldn't `git annex whereis large.bin`: %w", err) + return fmt.Errorf("Couldn't `git annex whereis`: %w", err) } // Note: this regex is unanchored because 'whereis' outputs multiple lines containing // headers and 1+ remotes and we just want to find one of them. match = regexp.MustCompile(regexp.QuoteMeta(remoteAnnexUUID) + " -- .* \\[origin\\]\n").MatchString(annexWhereis) if !match { - return errors.New("'git annex whereis' should report large.bin is known to be in [origin]") + return errors.New("'git annex whereis' should report files are known to be in [origin]") } return nil @@ -791,27 +850,56 @@ func doAnnexDownloadTest(remoteRepoPath, repoPath string) (err error) { return err } - // verify the file was downloaded - localObjectPath, err := contentLocation(repoPath, "large.bin") - if err != nil { - return err - } - // localObjectPath := path.Join(repoPath, "large.bin") // or, just compare against the checked-out file + // verify the files downloaded - remoteObjectPath, err := contentLocation(remoteRepoPath, "large.bin") + cmp := func(filename string) error { + localObjectPath, err := contentLocation(repoPath, filename) + if err != nil { + return err + } + // localObjectPath := path.Join(repoPath, filename) // or, just compare against the checked-out file + + remoteObjectPath, err := contentLocation(remoteRepoPath, filename) + if err != nil { + return err + } + + match, err := tests.FileCmp(localObjectPath, remoteObjectPath, 0) + if err != nil { + return err + } + if !match { + return errors.New("Annexed files should be the same") + } + + return nil + } + + // this is the annex-symlink file + stat, err := os.Lstat(path.Join(repoPath, "annexed.tiff")) if err != nil { + return fmt.Errorf("Lstat: %w", err) + } + if !((stat.Mode() & os.ModeSymlink) != 0) { + // this line is really just double-checking that the text fixture is set up correctly + return errors.New("*.tiff should be a symlink") + } + if err = cmp("annexed.tiff"); err != nil { return err } - match, err := tests.FileCmp(localObjectPath, remoteObjectPath, 0) + // this is the annex-pointer file + stat, err = os.Lstat(path.Join(repoPath, "annexed.bin")) if err != nil { - return err + return fmt.Errorf("Lstat: %w", err) } - if !match { - return errors.New("Annexed files should be the same") + if !((stat.Mode() & os.ModeSymlink) == 0) { + // this line is really just double-checking that the text fixture is set up correctly + return errors.New("*.bin should not be a symlink") } + err = cmp("annexed.bin") - return nil + return err } func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { @@ -956,16 +1044,36 @@ func doInitAnnexRepository(repoPath string) error { return err } - // add a file to the annex - err = generateRandomFile(1024*1024/4, path.Join(repoPath, "large.bin")) + // add files to the annex, stored via annex symlinks + // // a binary file + err = generateRandomFile(1024*1024/4, path.Join(repoPath, "annexed.tiff")) if err != nil { return err } + + err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "add", ".").Run(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + + // add files to the annex, stored via git-annex-smudge + // // a binary file + err = generateRandomFile(1024*1024/4, path.Join(repoPath, "annexed.bin")) + if err != nil { + return err + } + + if err != nil { + return err + } + err = git.AddChanges(repoPath, false, ".") if err != nil { return err } - err = git.CommitChanges(repoPath, git.CommitChangesOptions{Message: "Annex a file"}) + + // save everything + err = git.CommitChanges(repoPath, git.CommitChangesOptions{Message: "Annex files"}) if err != nil { return err } From 3a689a351461689d453cc7e8e4597e17c3536a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 24 May 2024 13:21:20 +0200 Subject: [PATCH 028/115] Adapt patch to upstream changes Test with different objectFormats. --- tests/integration/git_annex_test.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index df933add3a..d9f056f056 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -65,19 +65,21 @@ func TestGitAnnexMedia(t *testing.T) { } onGiteaRun(t, func(t *testing.T, u *url.URL) { - ctx := NewAPITestContext(t, "user2", "annex-media-test", auth_model.AccessTokenScopeWriteRepository) + forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { + ctx := NewAPITestContext(t, "user2", "annex-media-test"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository) - // create a public repo - require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false)) + // create a public repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false, objectFormat)) - // the filenames here correspond to specific cases defined in doInitAnnexRepository() - t.Run("AnnexSymlink", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - doAnnexMediaTest(t, ctx, "annexed.tiff") - }) - t.Run("AnnexPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - doAnnexMediaTest(t, ctx, "annexed.bin") + // the filenames here correspond to specific cases defined in doInitAnnexRepository() + t.Run("AnnexSymlink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doAnnexMediaTest(t, ctx, "annexed.tiff") + }) + t.Run("AnnexPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doAnnexMediaTest(t, ctx, "annexed.bin") + }) }) }) } From 276114f289e80d0bf05b2afffd1c723f4b3ee1d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Tue, 14 May 2024 14:34:44 +0200 Subject: [PATCH 029/115] Adapt patch to upstream changes Use tests.FileCmp instead of util.FileCmp. --- tests/integration/git_annex_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index d9f056f056..3e54ace2f2 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -113,7 +113,7 @@ func doAnnexMediaTest(t *testing.T, ctx APITestContext, file string) { fd.Close() // verify the download - match, err := util.FileCmp(localObjectPath, remoteObjectPath, 0) + match, err := tests.FileCmp(localObjectPath, remoteObjectPath, 0) require.NoError(t, err) require.True(t, match, "Annexed files should be the same") } From 3b7db37075270ac277e5e98090c45fe269af92ac Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 27 Nov 2022 02:13:46 -0500 Subject: [PATCH 030/115] git-annex: views for annex files This updates the repo index/file view endpoints so annex files match the way LFS files are rendered, making annexed files accessible via the web instead of being black boxes only accessible by git clone. This mostly just duplicates the existing LFS logic. It doesn't try to combine itself with the existing logic, to make merging with upstream easier. If upstream ever decides to accept, I would like to try to merge the redundant logic. The one bit that doesn't directly copy LFS is my choice to hide annex-symlinks. LFS files are always _pointer files_ and therefore always render with the "file" icon and no special label, but annex files come in two flavours: symlinks or pointer files. I've conflated both kinds to try to give a consistent experience. The tests in here ensure the correct download link (/media, from the last PR) renders in both the toolbar and, if a binary file (like most annexed files will be), in the main pane, but it also adds quite a bit of code to make sure text files that happen to be annexed are dug out and rendered inline like LFS files are. --- modules/base/tool.go | 7 ++ options/locale/locale_cs-CZ.ini | 2 + options/locale/locale_de-DE.ini | 2 + options/locale/locale_el-GR.ini | 2 + options/locale/locale_en-US.ini | 2 + options/locale/locale_es-ES.ini | 2 + options/locale/locale_fa-IR.ini | 2 + options/locale/locale_fr-FR.ini | 2 + options/locale/locale_hu-HU.ini | 2 + options/locale/locale_id-ID.ini | 2 + options/locale/locale_is-IS.ini | 1 + options/locale/locale_it-IT.ini | 2 + options/locale/locale_ja-JP.ini | 2 + options/locale/locale_ko-KR.ini | 1 + options/locale/locale_lv-LV.ini | 4 +- options/locale/locale_nl-NL.ini | 2 + options/locale/locale_pl-PL.ini | 2 + options/locale/locale_pt-BR.ini | 2 + options/locale/locale_pt-PT.ini | 2 + options/locale/locale_ru-RU.ini | 2 + options/locale/locale_si-LK.ini | 2 + options/locale/locale_sk-SK.ini | 1 + options/locale/locale_sv-SE.ini | 2 + options/locale/locale_tr-TR.ini | 2 + options/locale/locale_uk-UA.ini | 2 + options/locale/locale_zh-CN.ini | 2 + options/locale/locale_zh-HK.ini | 3 +- options/locale/locale_zh-TW.ini | 2 + routers/web/repo/view.go | 66 +++++++++++--- templates/repo/file_info.tmpl | 1 + tests/integration/git_annex_test.go | 133 ++++++++++++++++++++++++++++ 31 files changed, 248 insertions(+), 13 deletions(-) diff --git a/modules/base/tool.go b/modules/base/tool.go index fd6a7c2b77..e70d7d4df7 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -16,6 +16,7 @@ import ( "strings" "unicode/utf8" + "forgejo.org/modules/annex" "forgejo.org/modules/git" "forgejo.org/modules/log" @@ -103,6 +104,12 @@ func Int64sToStrings(ints []int64) []string { func EntryIcon(entry *git.TreeEntry) string { switch { case entry.IsLink(): + isAnnexed, _ := annex.IsAnnexed(entry.Blob()) + if isAnnexed { + // git-annex files are sometimes stored as symlinks; + // short-circuit that so like LFS they are displayed as regular files + return "file" + } te, _, err := entry.FollowLink() if err != nil { log.Debug(err.Error()) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index e61c3fa90d..d342d5fcb2 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1349,6 +1349,7 @@ view_git_blame=Zobrazit git blame video_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „video“. audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku HTML5 „audio“. stored_lfs=Uloženo pomocí Git LFS +stored_annex=Uloženo pomocí Git Annex symbolic_link=Symbolický odkaz executable_file=Spustitelný soubor vendored = Vendorováno @@ -1374,6 +1375,7 @@ editor.upload_file=Nahrát soubor editor.edit_file=Upravit soubor editor.preview_changes=Náhled změn editor.cannot_edit_lfs_files=Soubory LFS nemohou být upravovány přes webové rozhraní. +editor.cannot_edit_annex_files=Annex soubory nemohou být upravovány přes webové rozhraní. editor.cannot_edit_non_text_files=Binární soubory nemohou být upravovány přes webové rozhraní. editor.edit_this_file=Upravit soubor editor.this_file_locked=Soubor je uzamčen diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index c4801a2fef..26f24ab6c7 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1349,6 +1349,7 @@ view_git_blame=„git blame“ ansehen video_not_supported_in_browser=Dein Browser unterstützt das HTML5-„video“-Tag nicht. audio_not_supported_in_browser=Dein Browser unterstützt das HTML5-„audio“-Tag nicht. stored_lfs=Gespeichert mit Git LFS +stored_annex=Gespeichert mit Git Annex symbolic_link=Softlink executable_file=Ausführbare Datei commit_graph=Commit-Graph @@ -1372,6 +1373,7 @@ editor.upload_file=Datei hochladen editor.edit_file=Datei bearbeiten editor.preview_changes=Vorschau der Änderungen editor.cannot_edit_lfs_files=LFS-Dateien können im Webinterface nicht bearbeitet werden. +editor.cannot_edit_annex_files=Annex-Dateien können im Webinterface nicht bearbeitet werden. editor.cannot_edit_non_text_files=Binärdateien können nicht im Webinterface bearbeitet werden. editor.edit_this_file=Datei bearbeiten editor.this_file_locked=Datei ist gesperrt diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index c20a2f3172..4eb1322cea 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -1321,6 +1321,7 @@ view_git_blame=Προβολή git blame video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «video». audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 «audio». stored_lfs=Αποθηκεύτηκε με το Git LFS +stored_annex=Αποθηκεύτηκε με το Git Annex symbolic_link=Symbolic link executable_file=Εκτελέσιμο αρχείο commit_graph=Γράφημα commit @@ -1344,6 +1345,7 @@ editor.upload_file=Ανέβασμα αρχείου editor.edit_file=Επεξεργασία αρχείου editor.preview_changes=Προεπισκόπηση αλλαγών editor.cannot_edit_lfs_files=Τα αρχεία LFS δεν μπορούν να επεξεργαστούν στη διεπαφή web. +editor.cannot_edit_annex_files=Τα αρχεία Annex δεν μπορούν να επεξεργαστούν στη διεπαφή web. editor.cannot_edit_non_text_files=Τα δυαδικά αρχεία δεν μπορούν να επεξεργαστούν στη διεπαφή web. editor.edit_this_file=Επεξεργασία αρχείου editor.this_file_locked=Το αρχείο είναι κλειδωμένο diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index bfdcdb9112..5acf932e23 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1367,6 +1367,7 @@ view_git_blame = View git blame video_not_supported_in_browser = Your browser does not support the HTML5 "video" tag. audio_not_supported_in_browser = Your browser does not support the HTML5 "audio" tag. stored_lfs = Stored with Git LFS +stored_annex = Stored with Git Annex symbolic_link = Symbolic link executable_file = Executable file vendored = Vendored @@ -1394,6 +1395,7 @@ editor.upload_file = Upload file editor.edit_file = Edit file editor.preview_changes = Preview changes editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface. +editor.cannot_edit_annex_files = Annex files cannot be edited in the web interface. editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface. editor.edit_this_file = Edit file editor.this_file_locked = File is locked diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 3ff6c565b7..180dade48f 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -1340,6 +1340,7 @@ view_git_blame=Ver Git blame video_not_supported_in_browser=Su navegador no soporta el tag "video" de HTML5. audio_not_supported_in_browser=Su navegador no soporta el tag "audio" de HTML5. stored_lfs=Almacenados con Git LFS +stored_annex=Almacenados con Git Annex symbolic_link=Enlace simbólico executable_file=Archivo ejecutable commit_graph=Gráfico de commits @@ -1363,6 +1364,7 @@ editor.upload_file=Subir archivo editor.edit_file=Editar archivo editor.preview_changes=Vista previa de los cambios editor.cannot_edit_lfs_files=Los archivos LFS no se pueden editar en la interfaz web. +editor.cannot_edit_annex_files=Los archivos Annex no se pueden editar en la interfaz web. editor.cannot_edit_non_text_files=Los archivos binarios no se pueden editar en la interfaz web. editor.edit_this_file=Editar archivo editor.this_file_locked=El archivo está bloqueado diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 804b48b2b2..6b6a530639 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1036,6 +1036,7 @@ file_copy_permalink=پرمالینک را کپی کنید video_not_supported_in_browser=مرورگر شما از تگ video که در HTML5 تعریف شده است، پشتیبانی نمی کند. audio_not_supported_in_browser=مرورگر شما از تگ audio که در HTML5 تعریف شده است، پشتیبانی نمی کند. stored_lfs=ذخیره شده با GIT LFS +stored_annex=ذخیره شده با GIT Annex symbolic_link=پیوند نمادین commit_graph=نمودار کامیت commit_graph.select=انتخاب برنچها @@ -1053,6 +1054,7 @@ editor.upload_file=بارگذاری پرونده editor.edit_file=ویرایش پرونده editor.preview_changes=پیش نمایش تغییرات editor.cannot_edit_lfs_files=پرونده های LFS در صحفه وب قابل تغییر نیست. +editor.cannot_edit_annex_files=پرونده های Annex در صحفه وب قابل تغییر نیست. editor.cannot_edit_non_text_files=پرونده‎های دودویی در صفحه وب قابل تغییر نیست. editor.edit_this_file=ویرایش پرونده editor.this_file_locked=پرونده قفل شده است diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index ef4b7bad5d..717a204f8e 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1351,6 +1351,7 @@ view_git_blame=Voir Git blame video_not_supported_in_browser=Votre navigateur ne supporte pas la balise « vidéo » HTML5. audio_not_supported_in_browser=Votre navigateur ne supporte pas la balise « audio » HTML5. stored_lfs=Stocké avec Git LFS +stored_annex=Stocké avec Git Annex symbolic_link=Lien symbolique executable_file=Fichier exécutable vendored = Vendored @@ -1376,6 +1377,7 @@ editor.upload_file=Téléverser un fichier editor.edit_file=Modifier le fichier editor.preview_changes=Aperçu des modifications editor.cannot_edit_lfs_files=Les fichiers LFS ne peuvent pas être modifiés dans l'interface web. +editor.cannot_edit_annex_files=Les fichiers Annex ne peuvent pas être modifiés dans l'interface web. editor.cannot_edit_non_text_files=Les fichiers binaires ne peuvent pas être édités dans l'interface web. editor.edit_this_file=Modifier le fichier editor.this_file_locked=Le fichier est verrouillé diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 411bad835a..ea8732f422 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -787,6 +787,7 @@ file_too_large=Ez a fájl túl nagy ahhoz, hogy megjelenítsük. video_not_supported_in_browser=A böngésző nem támogatja a HTML5 video tag-et. audio_not_supported_in_browser=A böngésző nem támogatja a HTML5 audio tag-et. stored_lfs=Git LFS-el eltárolva +stored_annex=Git Annex-el eltárolva symbolic_link=Szimbolikus hivatkozás commit_graph=Commit gráf commit_graph.hide_pr_refs=Pull request-ek elrejtése @@ -799,6 +800,7 @@ editor.upload_file=Fájl feltöltése editor.edit_file=Fájl szerkesztése editor.preview_changes=Változások előnézete editor.cannot_edit_lfs_files=LFS fájlok nem szerkeszthetőek a webes felületen. +editor.cannot_edit_annex_files=Annex fájlok nem szerkeszthetőek a webes felületen. editor.cannot_edit_non_text_files=Bináris fájlok nem szerkeszthetőek a webes felületen. editor.edit_this_file=Fájl szerkesztése editor.this_file_locked=Zárolt állomány diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 673d1464b1..fc9d934ce6 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -681,6 +681,7 @@ file_permalink=Permalink file_too_large=Berkas terlalu besar untuk ditampilkan. stored_lfs=Tersimpan dengan GIT LFS +stored_annex=Tersimpan dengan GIT Annex commit_graph=Grafik Komit blame=Salahkan normal_view=Pandangan Normal @@ -692,6 +693,7 @@ editor.upload_file=Unggah Berkas editor.edit_file=Sunting Berkas editor.preview_changes=Tinjau Perubahan editor.cannot_edit_lfs_files=Berkas LFS tidak dapat disunting dalam antarmuka web. +editor.cannot_edit_annex_files=Berkas Annex tidak dapat disunting dalam antarmuka web. editor.cannot_edit_non_text_files=Berkas biner tidak dapat disunting dalam antarmuka web. editor.edit_this_file=Sunting Berkas editor.this_file_locked=Berkas terkunci diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index 9b1d56fed9..9c39b9d830 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -684,6 +684,7 @@ file_view_rendered=Skoða Unnið file_copy_permalink=Afrita Varanlega Slóð stored_lfs=Geymt með Git LFS +stored_annex=Geymt með Git Annex commit_graph.hide_pr_refs=Fela Sameiningarbeiðnir commit_graph.monochrome=Einlitað commit_graph.color=Litað diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index c4083f0ce8..3c5e834260 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1277,6 +1277,7 @@ view_git_blame=Visualizza git incolpa video_not_supported_in_browser=Il tuo browser non supporta le etichette "video" di HTML5. audio_not_supported_in_browser=Il tuo browser non supporta le etichette "audio" di HTML5. stored_lfs=Memorizzati con Git LFS +stored_annex=Memorizzati con Git Annex symbolic_link=Link Simbolico commit_graph=Grafico dei commit commit_graph.select=Seleziona rami @@ -1295,6 +1296,7 @@ editor.upload_file=Carica file editor.edit_file=Modifica file editor.preview_changes=Anteprima modifiche editor.cannot_edit_lfs_files=I file LFS non possono essere modificati nell'interfaccia web. +editor.cannot_edit_annex_files=I file Annex non possono essere modificati nell'interfaccia web. editor.cannot_edit_non_text_files=I file binari non possono essere modificati tramite interfaccia web. editor.edit_this_file=Modifica file editor.this_file_locked=Il file è bloccato diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index d4d7024f5d..32caac1371 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1307,6 +1307,7 @@ view_git_blame=Git Blameを表示 video_not_supported_in_browser=このブラウザはHTML5のvideoタグをサポートしていません。 audio_not_supported_in_browser=このブラウザーはHTML5のaudioタグをサポートしていません。 stored_lfs=Git LFSで保管されています +stored_annex=Git Annexで保管されています symbolic_link=シンボリック リンク executable_file=実行ファイル commit_graph=コミットグラフ @@ -1330,6 +1331,7 @@ editor.upload_file=ファイルをアップロード editor.edit_file=ファイルを編集 editor.preview_changes=変更をプレビュー editor.cannot_edit_lfs_files=LFSのファイルはWebインターフェースで編集できません。 +editor.cannot_edit_annex_files=AnnexのファイルはWebインターフェースで編集できません。 editor.cannot_edit_non_text_files=バイナリファイルはWebインターフェースで編集できません。 editor.edit_this_file=ファイルを編集 editor.this_file_locked=ファイルはロックされています diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 5e31ae4f19..7e7871777d 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -811,6 +811,7 @@ file_too_large=보여주기에는 파일이 너무 큽니다. video_not_supported_in_browser=당신의 브라우저가 HTML5의 "video" 태그를 지원하지 않습니다. audio_not_supported_in_browser=당신의 브라우저가 HTML5의 "audio" 태그를 지원하지 않습니다. stored_lfs=Git LFS에 저장되어 있습니다 +stored_annex=Git Annex에 저장되어 있습니다 commit_graph=커밋 그래프 editor.new_file=새 파일 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 70ce31ec4f..e4b51bbfb7 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -1348,6 +1348,7 @@ view_git_blame=Apskatīt Git izmaiņu veicējus video_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "video". audio_not_supported_in_browser=Pārlūks neatbalsta HTML5 tagu "audio". stored_lfs=Saglabāts Git LFS +stored_annex=Saglabāts Git Annex symbolic_link=Simboliska saite executable_file=Izpildāma datne commit_graph=Iesūtījumu karte @@ -1371,6 +1372,7 @@ editor.upload_file=Augšupielādēt datni editor.edit_file=Labot datni editor.preview_changes=Priekšskatīt izmaiņas editor.cannot_edit_lfs_files=LFS datnes nevar labot tīmekļa saskarnē. +editor.cannot_edit_annex_files=Annex datnes tīmekļa saskarnē nevar labot. editor.cannot_edit_non_text_files=Binārās datnes nevar labot tīmekļa saskarnē. editor.edit_this_file=Labot datni editor.this_file_locked=Datne ir slēgta @@ -4072,4 +4074,4 @@ filepreview.lines = %[1]d. līdz %[2]d. rinda %[3]s filepreview.truncated = Priekšskatījums tika saīsināts [translation_meta] -test = Šī ir pārbaudes virkne. Tā netiek attēlota Forgejo saskarnē, bet tiek izmantota pārbaudes nolūkiem. Droši var ievadīt "ok", lai ietaupītu laiku (vai kādu jautru faktu pēc izvēles), lai sasniegtu to saldo 100% pabeigšanas atzīmi. \ No newline at end of file +test = Šī ir pārbaudes virkne. Tā netiek attēlota Forgejo saskarnē, bet tiek izmantota pārbaudes nolūkiem. Droši var ievadīt "ok", lai ietaupītu laiku (vai kādu jautru faktu pēc izvēles), lai sasniegtu to saldo 100% pabeigšanas atzīmi. diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index bfd6c6878e..f68f738729 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1316,6 +1316,7 @@ view_git_blame=Bekijk git blame video_not_supported_in_browser=Uw browser ondersteunt de HTML5 "video" element niet. audio_not_supported_in_browser=Uw browser ondersteunt de HTML5 "audio" element niet. stored_lfs=Opgeslagen met Git LFS +stored_annex=Opgeslagen met Git Annex symbolic_link=Symbolische link commit_graph=Commit grafiek commit_graph.select=Selecteer branches @@ -1334,6 +1335,7 @@ editor.upload_file=Upload bestand editor.edit_file=Bewerk bestand editor.preview_changes=Voorbeeld tonen editor.cannot_edit_lfs_files=LFS-bestanden kunnen niet worden bewerkt in de webinterface. +editor.cannot_edit_annex_files=Annex-bestanden kunnen niet worden bewerkt in de webinterface. editor.cannot_edit_non_text_files=Binaire bestanden kunnen niet worden bewerkt in de webinterface. editor.edit_this_file=Bewerk bestand editor.this_file_locked=Bestand is vergrendeld diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 8aaa680009..640e031956 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -1255,6 +1255,7 @@ file_copy_permalink=Kopiuj bezpośredni odnośnik video_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "video". audio_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "audio". stored_lfs=Przechowane za pomocą Git LFS +stored_annex=Przechowane za pomocą Git Annex symbolic_link=Dowiązanie symboliczne commit_graph=Wykres commitów commit_graph.select=Wybierz gałęzie @@ -1272,6 +1273,7 @@ editor.upload_file=Wyślij plik editor.edit_file=Edytuj plik editor.preview_changes=Podgląd zmian editor.cannot_edit_lfs_files=Pliki LFS nie mogą być edytowane poprzez interfejs przeglądarkowy. +editor.cannot_edit_annex_files=Pliki Annex nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.cannot_edit_non_text_files=Pliki binarne nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.edit_this_file=Edytuj plik editor.this_file_locked=Plik jest zablokowany diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 92fa50d618..2bfe2613bf 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -1341,6 +1341,7 @@ view_git_blame=Ver git blame video_not_supported_in_browser=Seu navegador não tem suporte para a tag "video" do HTML5. audio_not_supported_in_browser=Seu navegador não tem suporte para a tag "audio" do HTML5. stored_lfs=Armazenado com Git LFS +stored_annex=Armazenado com Git Annex symbolic_link=Link simbólico executable_file=Arquivo executável commit_graph=Gráfico de commits @@ -1364,6 +1365,7 @@ editor.upload_file=Enviar arquivo editor.edit_file=Editar arquivo editor.preview_changes=Pré-visualizar alterações editor.cannot_edit_lfs_files=Arquivos LFS não podem ser editados na interface web. +editor.cannot_edit_annex_files=Arquivos Annex não podem ser editados na interface web. editor.cannot_edit_non_text_files=Arquivos binários não podem ser editados na interface web. editor.edit_this_file=Editar arquivo editor.this_file_locked=Arquivo está bloqueado diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 05613030b5..eea29f8850 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1353,6 +1353,7 @@ view_git_blame=Ver git blame video_not_supported_in_browser=O seu navegador não suporta a etiqueta "video" do HTML5. audio_not_supported_in_browser=O seu navegador não suporta a etiqueta "audio" do HTML5. stored_lfs=Armazenado com Git LFS +stored_annex=Armazenado com Git Annex symbolic_link=Ligação simbólica executable_file=Ficheiro executável vendored=Externo @@ -1378,6 +1379,7 @@ editor.upload_file=Carregar ficheiro editor.edit_file=Editar ficheiro editor.preview_changes=Pré-visualizar modificações editor.cannot_edit_lfs_files=Ficheiros LFS não podem ser editados na interface web. +editor.cannot_edit_annex_files=Ficheiros Annex não podem ser editados na interface web. editor.cannot_edit_non_text_files=Ficheiros binários não podem ser editados na interface da web. editor.edit_this_file=Editar ficheiro editor.this_file_locked=Ficheiro bloqueado diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 38d1d885cc..1d5c115e67 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1336,6 +1336,7 @@ view_git_blame=Показать git blame video_not_supported_in_browser=Ваш браузер не поддерживает тэг HTML5 «video». audio_not_supported_in_browser=Ваш браузер не поддерживает тэг HTML5 «audio». stored_lfs=Хранится Git LFS +stored_annex=Хранится Git Annex symbolic_link=Символическая ссылка executable_file=Исполняемый файл commit_graph=Граф коммитов @@ -1359,6 +1360,7 @@ editor.upload_file=Загрузить файл editor.edit_file=Редактировать файл editor.preview_changes=Просмотр изменений editor.cannot_edit_lfs_files=LFS файлы невозможно редактировать в веб-интерфейсе. +editor.cannot_edit_annex_files=Annex файлы невозможно редактировать в веб-интерфейсе. editor.cannot_edit_non_text_files=Двоичные файлы нельзя редактировать в веб-интерфейсе. editor.edit_this_file=Редактировать файл editor.this_file_locked=Файл заблокирован diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index d55b238b1c..6dbb6dc3c2 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -894,6 +894,7 @@ file_copy_permalink=පිටපත් මාමලින්ක් video_not_supported_in_browser=ඔබගේ බ්රව්සරය HTML5 'වීඩියෝ' ටැගය සඳහා සහය නොදක්වයි. audio_not_supported_in_browser=ඔබගේ බ්රව්සරය HTML5 'ශ්රව්ය' ටැගය සඳහා සහය නොදක්වයි. stored_lfs=Git LFS සමඟ ගබඩා +stored_annex=Git Annex සමඟ ගබඩා symbolic_link=සංකේතාත්මක සබැඳිය commit_graph=ප්රස්තාරය කැප commit_graph.select=ශාඛා තෝරන්න @@ -911,6 +912,7 @@ editor.upload_file=ගොනුව උඩුගත කරන්න editor.edit_file=ගොනුව සංස්කරණය editor.preview_changes=වෙනස්කම් පෙරදසුන editor.cannot_edit_lfs_files=LFS ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක. +editor.cannot_edit_annex_files=Annex ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක. editor.cannot_edit_non_text_files=ද්විමය ගොනු වෙබ් අතුරු මුහුණත තුළ සංස්කරණය කළ නොහැක. editor.edit_this_file=ගොනුව සංස්කරණය editor.this_file_locked=ගොනුවට අගුළු ලා ඇත diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index a90ddd513b..e43ba1ce37 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -1013,6 +1013,7 @@ view_git_blame=Zobraziť Git Blame video_not_supported_in_browser=Váš prehliadač nepodporuje HTML5 tag 'video'. audio_not_supported_in_browser=Váš prehliadač nepodporuje HTML5 tag 'audio'. stored_lfs=Uložené pomocou Git LFS +stored_annex=Uložené pomocou Git Annex symbolic_link=Symbolický odkaz commit_graph=Graf commitov line=riadok diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index cbb42b3caa..96e2ef2b84 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -907,6 +907,7 @@ file_too_large=Filen är för stor för att visas. video_not_supported_in_browser=Din webbläsare stödjer ej HTML5-taggen "video". audio_not_supported_in_browser=Din webbläsare stödjer ej HTML5-taggen "audio". stored_lfs=Sparad med Git LFS +stored_annex=Sparad med Git Annex symbolic_link=Symbolisk länk commit_graph=Commitgraf commit_graph.monochrome=Mono @@ -920,6 +921,7 @@ editor.upload_file=Ladda upp fil editor.edit_file=Redigera fil editor.preview_changes=Förhandsgranska ändringar editor.cannot_edit_lfs_files=LFS-filer kan inte redigeras i webbgränssnittet. +editor.cannot_edit_annex_files=Annex-filer kan inte redigeras i webbgränssnittet. editor.cannot_edit_non_text_files=Binära filer kan inte redigeras genom webbgränssnittet. editor.edit_this_file=Redigera fil editor.this_file_locked=Filen är låst diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 2743f63a41..b9fbc9c52f 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1289,6 +1289,7 @@ view_git_blame=Git Suç Görüntüle video_not_supported_in_browser=Tarayıcınız HTML5 'video' etiketini desteklemiyor. audio_not_supported_in_browser=Tarayıcınız HTML5 'audio' etiketini desteklemiyor. stored_lfs=Git LFS ile depolandı +stored_annex=Git Annex ile depolandı symbolic_link=Sembolik Bağlantı executable_file=Çalıştırılabilir Dosya commit_graph=İşleme Grafiği @@ -1312,6 +1313,7 @@ editor.upload_file=Dosya Yükle editor.edit_file=Dosyayı Düzenle editor.preview_changes=Değişiklikleri Önizle editor.cannot_edit_lfs_files=LFS dosyaları web arayüzünde düzenlenemez. +editor.cannot_edit_annex_files=Annex dosyaları web arayüzünde düzenlenemez. editor.cannot_edit_non_text_files=Bu tür dosyalar web arayüzünden düzenlenemez. editor.edit_this_file=Dosyayı Düzenle editor.this_file_locked=Dosya kilitlendi diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 7d3d6f4c8b..0bd3a937a4 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1285,6 +1285,7 @@ file_copy_permalink=Копіювати постійне посилання video_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 «video». audio_not_supported_in_browser=Ваш браузер не підтримує тег HTML5 «audio». stored_lfs=Збережено з Git LFS +stored_annex=Збережено з Git Annex symbolic_link=Символічне посилання commit_graph=Графік комітів commit_graph.select=Виберіть гілки @@ -1302,6 +1303,7 @@ editor.upload_file=Завантажити файл editor.edit_file=Редагувати файл editor.preview_changes=Попередній перегляд змін editor.cannot_edit_lfs_files=Файли LFS не можна редагувати в веб-інтерфейсі. +editor.cannot_edit_annex_files=Файли Annex не можна редагувати в веб-інтерфейсі. editor.cannot_edit_non_text_files=Бінарні файли не можливо редагувати у веб-інтерфейсі. editor.edit_this_file=Редагувати файл editor.this_file_locked=Файл заблоковано diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 2e062d80b4..226f1be3b0 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1351,6 +1351,7 @@ view_git_blame=查看 Git Blame video_not_supported_in_browser=您的浏览器不支持 HTML5 “video” 标签。 audio_not_supported_in_browser=您的浏览器不支持 HTML5 “audio” 标签。 stored_lfs=存储到Git LFS +stored_annex=存储到Git Annex symbolic_link=符号链接 executable_file=可执行文件 vendored = Vendored @@ -1376,6 +1377,7 @@ editor.upload_file=上传文件 editor.edit_file=编辑文件 editor.preview_changes=预览变更 editor.cannot_edit_lfs_files=无法在 web 界面中编辑 lfs 文件。 +editor.cannot_edit_annex_files=无法在 web 界面中编辑 lfs 文件。 editor.cannot_edit_non_text_files=网页不能编辑二进制文件。 editor.edit_this_file=编辑文件 editor.this_file_locked=文件已锁定 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index e2cb0d8b2c..93744cbbcf 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -488,6 +488,7 @@ file_view_raw=查看原始文件 file_permalink=永久連結 stored_lfs=儲存到到 Git LFS +stored_annex=儲存到到 Git Annex editor.preview_changes=預覽更改 editor.or=或 @@ -1154,4 +1155,4 @@ runners.labels = 標籤 [git.filemode] -[search] \ No newline at end of file +[search] diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index ea8c1bc2b1..51c9ba7139 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -1292,6 +1292,7 @@ view_git_blame=檢視 Git Blame video_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「video」標籤。 audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「audio」標籤。 stored_lfs=已使用 Git LFS 儲存 +stored_annex=已使用 Git Annex 儲存 symbolic_link=符號連結 commit_graph=提交線圖 commit_graph.select=選擇分支 @@ -1311,6 +1312,7 @@ editor.upload_file=上傳檔案 editor.edit_file=編輯檔案 editor.preview_changes=預覽變更 editor.cannot_edit_lfs_files=無法在 web 介面中編輯 LFS 檔。 +editor.cannot_edit_annex_files=無法在 web 介面中編輯 Annex 檔。 editor.cannot_edit_non_text_files=網站介面不能編輯二進位檔案。 editor.edit_this_file=編輯檔案 editor.this_file_locked=檔案已被鎖定 diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index bea002f690..380218ec31 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -34,6 +34,7 @@ import ( unit_model "forgejo.org/models/unit" user_model "forgejo.org/models/user" "forgejo.org/modules/actions" + "forgejo.org/modules/annex" "forgejo.org/modules/base" "forgejo.org/modules/charset" "forgejo.org/modules/git" @@ -209,14 +210,47 @@ func localizedExtensions(ext, languageCode string) (localizedExts []string) { } type fileInfo struct { - isTextFile bool - isLFSFile bool - fileSize int64 - lfsMeta *lfs.Pointer - st typesniffer.SniffedType + isTextFile bool + isLFSFile bool + isAnnexFile bool + fileSize int64 + lfsMeta *lfs.Pointer + st typesniffer.SniffedType } func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, io.ReadCloser, *fileInfo, error) { + isAnnexed, err := annex.IsAnnexed(blob) + if err != nil { + return nil, nil, nil, err + } + if isAnnexed { + // TODO: this code could be merged with the LFS case, especially the redundant type sniffer, + // but it is *currently* written this way to make merging with the non-annex upstream easier: + // this way, the git-annex patch is (mostly) pure additions. + + annexContent, err := annex.Content(blob) + if err != nil { + // in the case where annex content is missing, what should happen? + // do we render the page with an error message? + // actually that's not a bad idea, there's some sort of error message situation + // TODO: display an error to the user explaining that their data is missing + return nil, nil, nil, err + } + + stat, err := annexContent.Stat() + if err != nil { + return nil, nil, nil, err + } + + buf := make([]byte, 1024) + n, _ := util.ReadAtMost(annexContent, buf) + buf = buf[:n] + + st := typesniffer.DetectContentType(buf) + + return buf, annexContent, &fileInfo{st.IsText(), false, true, stat.Size(), nil, st}, nil + } + dataRc, err := blob.DataAsync() if err != nil { return nil, nil, nil, err @@ -231,18 +265,18 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, // FIXME: what happens when README file is an image? if !isTextFile || !setting.LFS.StartServer { - return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil + return buf, dataRc, &fileInfo{isTextFile, false, false, blob.Size(), nil, st}, nil } pointer, _ := lfs.ReadPointerFromBuffer(buf) if !pointer.IsValid() { // fallback to plain file - return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil + return buf, dataRc, &fileInfo{isTextFile, false, false, blob.Size(), nil, st}, nil } meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid) if err != nil { // fallback to plain file log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err) - return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil + return buf, dataRc, &fileInfo{isTextFile, false, false, blob.Size(), nil, st}, nil } dataRc.Close() @@ -262,7 +296,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, st = typesniffer.DetectContentType(buf) - return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil + return buf, dataRc, &fileInfo{st.IsText(), true, false, meta.Size, &meta.Pointer, st}, nil } func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) { @@ -451,10 +485,17 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { isDisplayingSource := ctx.FormString("display") == "source" isDisplayingRendered := !isDisplayingSource - if fInfo.isLFSFile { + if fInfo.isLFSFile || fInfo.isAnnexFile { ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } + if fInfo.isAnnexFile { + // pre-git-annex v7, all annexed files were represented in-repo as symlinks; + // but we pretend they aren't, since that's a distracting quirk of git-annex + // and not a meaningful choice on the user's part + ctx.Data["FileIsSymlink"] = false + } + isRepresentableAsText := fInfo.st.IsRepresentableAsText() if !isRepresentableAsText { // If we can't show plain text, always try to render. @@ -462,6 +503,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { isDisplayingRendered = true } ctx.Data["IsLFSFile"] = fInfo.isLFSFile + ctx.Data["IsAnnexFile"] = fInfo.isAnnexFile ctx.Data["FileSize"] = fInfo.fileSize ctx.Data["IsTextFile"] = fInfo.isTextFile ctx.Data["IsRepresentableAsText"] = isRepresentableAsText @@ -496,6 +538,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { // Assume file is not editable first. if fInfo.isLFSFile { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files") + } else if fInfo.isAnnexFile { + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_annex_files") } else if !isRepresentableAsText { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") } @@ -603,7 +647,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["FileContent"] = fileContent ctx.Data["LineEscapeStatus"] = statuses } - if !fInfo.isLFSFile { + if !fInfo.isLFSFile && !fInfo.isAnnexFile { if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) { if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { ctx.Data["CanEditFile"] = false diff --git a/templates/repo/file_info.tmpl b/templates/repo/file_info.tmpl index 6ae7c15a26..05d9825cfb 100644 --- a/templates/repo/file_info.tmpl +++ b/templates/repo/file_info.tmpl @@ -17,6 +17,7 @@ {{if .FileSize}}
    {{ctx.Locale.TrSize .FileSize}}{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}} + {{if .IsAnnexFile}} ({{ctx.Locale.Tr "repo.stored_annex"}}){{end}}
    {{end}} {{if .LFSLock}} diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 3e54ace2f2..2a016c61dc 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -118,6 +118,111 @@ func doAnnexMediaTest(t *testing.T, ctx APITestContext, file string) { require.True(t, match, "Annexed files should be the same") } +func TestGitAnnexViews(t *testing.T) { + if !setting.Annex.Enabled { + t.Skip("Skipping since annex support is disabled.") + } + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx := NewAPITestContext(t, "user2", "annex-template-render-test", auth_model.AccessTokenScopeWriteRepository) + + // create a public repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false)) + + session := loginUser(t, ctx.Username) + + t.Run("Index", func(t *testing.T) { + // test that annex symlinks renders with the _file icon_ on the main list + defer tests.PrintCurrentTest(t)() + + repoLink := path.Join("/", ctx.Username, ctx.Reponame) + req := NewRequest(t, "GET", repoLink) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + isFileIcon := htmlDoc.Find("tr[data-entryname='annexed.tiff'] > td.name svg").HasClass("octicon-file") + require.True(t, isFileIcon, "annexed files should render a plain file icon, even when stored via annex symlink") + }) + + t.Run("View", func(t *testing.T) { + // test how routers/web/repo/view.go + templates/repo/view_file.tmpl handle annexed files + defer tests.PrintCurrentTest(t)() + + doViewTest := func(file string) (htmlDoc *HTMLDoc, viewLink, mediaLink string) { + viewLink = path.Join("/", ctx.Username, ctx.Reponame, "/src/branch/master", file) + // rawLink := strings.Replace(viewLink, "/src/", "/raw/", 1) // TODO: do something with this? + mediaLink = strings.Replace(viewLink, "/src/", "/media/", 1) + + req := NewRequest(t, "GET", viewLink) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc = NewHTMLParser(t, resp.Body) + // the first button on the toolbar on the view template is the "Raw" button + // this CSS selector is the most precise I can think to use + buttonLink, exists := htmlDoc.Find(".file-header").Find("a[download]").Attr("href") + require.True(t, exists, "Download button should exist on the file header") + require.EqualValues(t, mediaLink, buttonLink, "Download link should use /media URL for annex files") + + return htmlDoc, viewLink, mediaLink + } + + t.Run("Binary", func(t *testing.T) { + // test that annexing a file renders the /media link in /src and NOT the /raw link + defer tests.PrintCurrentTest(t)() + + doBinaryViewTest := func(file string) { + htmlDoc, _, mediaLink := doViewTest(file) + + rawLink, exists := htmlDoc.Find("div.file-view > div.view-raw > a").Attr("href") + require.True(t, exists, "Download link should render instead of content because this is a binary file") + require.EqualValues(t, mediaLink, rawLink) + } + + t.Run("AnnexSymlink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doBinaryViewTest("annexed.tiff") + }) + t.Run("AnnexPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doBinaryViewTest("annexed.bin") + }) + }) + + t.Run("Text", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + doTextViewTest := func(file string) { + htmlDoc, _, _ := doViewTest(file) + require.True(t, htmlDoc.Find("div.file-view").Is(".code-view"), "should render as code") + } + + t.Run("AnnexSymlink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doTextViewTest("annexed.txt") + + t.Run("Markdown", func(t *testing.T) { + // special case: check that markdown can be pulled out of the annex and rendered, too + defer tests.PrintCurrentTest(t)() + htmlDoc, _, _ := doViewTest("annexed.md") + require.True(t, htmlDoc.Find("div.file-view").Is(".markdown"), "should render as markdown") + }) + }) + t.Run("AnnexPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doTextViewTest("annexed.rst") + + t.Run("Markdown", func(t *testing.T) { + // special case: check that markdown can be pulled out of the annex and rendered, too + defer tests.PrintCurrentTest(t)() + htmlDoc, _, _ := doViewTest("annexed.markdown") + require.True(t, htmlDoc.Find("div.file-view").Is(".markdown"), "should render as markdown") + }) + }) + }) + }) + }) +} + /* Test that permissions are enforced on git-annex-shell commands. @@ -1029,6 +1134,14 @@ func doInitAnnexRepository(repoPath string) error { if err != nil { return err } + _, err = f.WriteString("*.rst filter=annex\n") + if err != nil { + return err + } + _, err = f.WriteString("*.markdown filter=annex\n") + if err != nil { + return err + } f.Close() err = git.AddChanges(repoPath, false, ".") @@ -1053,6 +1166,18 @@ func doInitAnnexRepository(repoPath string) error { return err } + // // a text file + err = os.WriteFile(path.Join(repoPath, "annexed.md"), []byte("Overview\n=====\n\n1. Profit\n2. ???\n3. Review Life Activations\n"), 0o777) + if err != nil { + return err + } + + // // a markdown file + err = os.WriteFile(path.Join(repoPath, "annexed.txt"), []byte("We're going to see the wizard\nThe wonderful\nMonkey of\nBoz\n"), 0o777) + if err != nil { + return err + } + err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "add", ".").Run(&git.RunOpts{Dir: repoPath}) if err != nil { return err @@ -1065,6 +1190,14 @@ func doInitAnnexRepository(repoPath string) error { return err } + // // a text file + err = os.WriteFile(path.Join(repoPath, "annexed.rst"), []byte("Title\n=====\n\n- this is to test annexing a text file\n- lists are fun\n"), 0o777) + if err != nil { + return err + } + + // // a markdown file + err = os.WriteFile(path.Join(repoPath, "annexed.markdown"), []byte("Overview\n=====\n\n1. Profit\n2. ???\n3. Review Life Activations\n"), 0o777) if err != nil { return err } From aef3b8b31f2e6e508ba66e9eb33b15042633df57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 24 May 2024 13:21:42 +0200 Subject: [PATCH 031/115] Adapt patch to upstream changes Test with different objectFormats. --- tests/integration/git_annex_test.go | 148 ++++++++++++++-------------- 1 file changed, 75 insertions(+), 73 deletions(-) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 2a016c61dc..db1bb8a498 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -124,98 +124,100 @@ func TestGitAnnexViews(t *testing.T) { } onGiteaRun(t, func(t *testing.T, u *url.URL) { - ctx := NewAPITestContext(t, "user2", "annex-template-render-test", auth_model.AccessTokenScopeWriteRepository) + forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { + ctx := NewAPITestContext(t, "user2", "annex-template-render-test"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository) - // create a public repo - require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false)) + // create a public repo + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false, objectFormat)) - session := loginUser(t, ctx.Username) + session := loginUser(t, ctx.Username) - t.Run("Index", func(t *testing.T) { - // test that annex symlinks renders with the _file icon_ on the main list - defer tests.PrintCurrentTest(t)() + t.Run("Index", func(t *testing.T) { + // test that annex symlinks renders with the _file icon_ on the main list + defer tests.PrintCurrentTest(t)() - repoLink := path.Join("/", ctx.Username, ctx.Reponame) - req := NewRequest(t, "GET", repoLink) - resp := session.MakeRequest(t, req, http.StatusOK) - - htmlDoc := NewHTMLParser(t, resp.Body) - isFileIcon := htmlDoc.Find("tr[data-entryname='annexed.tiff'] > td.name svg").HasClass("octicon-file") - require.True(t, isFileIcon, "annexed files should render a plain file icon, even when stored via annex symlink") - }) - - t.Run("View", func(t *testing.T) { - // test how routers/web/repo/view.go + templates/repo/view_file.tmpl handle annexed files - defer tests.PrintCurrentTest(t)() - - doViewTest := func(file string) (htmlDoc *HTMLDoc, viewLink, mediaLink string) { - viewLink = path.Join("/", ctx.Username, ctx.Reponame, "/src/branch/master", file) - // rawLink := strings.Replace(viewLink, "/src/", "/raw/", 1) // TODO: do something with this? - mediaLink = strings.Replace(viewLink, "/src/", "/media/", 1) - - req := NewRequest(t, "GET", viewLink) + repoLink := path.Join("/", ctx.Username, ctx.Reponame) + req := NewRequest(t, "GET", repoLink) resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - // the first button on the toolbar on the view template is the "Raw" button - // this CSS selector is the most precise I can think to use - buttonLink, exists := htmlDoc.Find(".file-header").Find("a[download]").Attr("href") - require.True(t, exists, "Download button should exist on the file header") - require.EqualValues(t, mediaLink, buttonLink, "Download link should use /media URL for annex files") - - return htmlDoc, viewLink, mediaLink - } - - t.Run("Binary", func(t *testing.T) { - // test that annexing a file renders the /media link in /src and NOT the /raw link - defer tests.PrintCurrentTest(t)() - - doBinaryViewTest := func(file string) { - htmlDoc, _, mediaLink := doViewTest(file) - - rawLink, exists := htmlDoc.Find("div.file-view > div.view-raw > a").Attr("href") - require.True(t, exists, "Download link should render instead of content because this is a binary file") - require.EqualValues(t, mediaLink, rawLink) - } - - t.Run("AnnexSymlink", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - doBinaryViewTest("annexed.tiff") - }) - t.Run("AnnexPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - doBinaryViewTest("annexed.bin") - }) + htmlDoc := NewHTMLParser(t, resp.Body) + isFileIcon := htmlDoc.Find("tr[data-entryname='annexed.tiff'] > td.name svg").HasClass("octicon-file") + require.True(t, isFileIcon, "annexed files should render a plain file icon, even when stored via annex symlink") }) - t.Run("Text", func(t *testing.T) { + t.Run("View", func(t *testing.T) { + // test how routers/web/repo/view.go + templates/repo/view_file.tmpl handle annexed files defer tests.PrintCurrentTest(t)() - doTextViewTest := func(file string) { - htmlDoc, _, _ := doViewTest(file) - require.True(t, htmlDoc.Find("div.file-view").Is(".code-view"), "should render as code") + doViewTest := func(file string) (htmlDoc *HTMLDoc, viewLink, mediaLink string) { + viewLink = path.Join("/", ctx.Username, ctx.Reponame, "/src/branch/master", file) + // rawLink := strings.Replace(viewLink, "/src/", "/raw/", 1) // TODO: do something with this? + mediaLink = strings.Replace(viewLink, "/src/", "/media/", 1) + + req := NewRequest(t, "GET", viewLink) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc = NewHTMLParser(t, resp.Body) + // the first button on the toolbar on the view template is the "Raw" button + // this CSS selector is the most precise I can think to use + buttonLink, exists := htmlDoc.Find(".file-header").Find("a[download]").Attr("href") + require.True(t, exists, "Download button should exist on the file header") + require.EqualValues(t, mediaLink, buttonLink, "Download link should use /media URL for annex files") + + return htmlDoc, viewLink, mediaLink } - t.Run("AnnexSymlink", func(t *testing.T) { + t.Run("Binary", func(t *testing.T) { + // test that annexing a file renders the /media link in /src and NOT the /raw link defer tests.PrintCurrentTest(t)() - doTextViewTest("annexed.txt") - t.Run("Markdown", func(t *testing.T) { - // special case: check that markdown can be pulled out of the annex and rendered, too + doBinaryViewTest := func(file string) { + htmlDoc, _, mediaLink := doViewTest(file) + + rawLink, exists := htmlDoc.Find("div.file-view > div.view-raw > a").Attr("href") + require.True(t, exists, "Download link should render instead of content because this is a binary file") + require.EqualValues(t, mediaLink, rawLink) + } + + t.Run("AnnexSymlink", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - htmlDoc, _, _ := doViewTest("annexed.md") - require.True(t, htmlDoc.Find("div.file-view").Is(".markdown"), "should render as markdown") + doBinaryViewTest("annexed.tiff") + }) + t.Run("AnnexPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doBinaryViewTest("annexed.bin") }) }) - t.Run("AnnexPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - doTextViewTest("annexed.rst") - t.Run("Markdown", func(t *testing.T) { - // special case: check that markdown can be pulled out of the annex and rendered, too + t.Run("Text", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + doTextViewTest := func(file string) { + htmlDoc, _, _ := doViewTest(file) + require.True(t, htmlDoc.Find("div.file-view").Is(".code-view"), "should render as code") + } + + t.Run("AnnexSymlink", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - htmlDoc, _, _ := doViewTest("annexed.markdown") - require.True(t, htmlDoc.Find("div.file-view").Is(".markdown"), "should render as markdown") + doTextViewTest("annexed.txt") + + t.Run("Markdown", func(t *testing.T) { + // special case: check that markdown can be pulled out of the annex and rendered, too + defer tests.PrintCurrentTest(t)() + htmlDoc, _, _ := doViewTest("annexed.md") + require.True(t, htmlDoc.Find("div.file-view").Is(".markdown"), "should render as markdown") + }) + }) + t.Run("AnnexPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + doTextViewTest("annexed.rst") + + t.Run("Markdown", func(t *testing.T) { + // special case: check that markdown can be pulled out of the annex and rendered, too + defer tests.PrintCurrentTest(t)() + htmlDoc, _, _ := doViewTest("annexed.markdown") + require.True(t, htmlDoc.Find("div.file-view").Is(".markdown"), "should render as markdown") + }) }) }) }) From ad8476c821863c0aef54e0bccaf4c9ce9b244204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Wed, 15 May 2024 10:55:12 +0200 Subject: [PATCH 032/115] Add git-annex to docker image --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 9a8877920a..70c649679d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,6 +78,7 @@ RUN apk --no-cache add \ sqlite \ su-exec \ gnupg \ + git-annex \ && rm -rf /var/cache/apk/* RUN addgroup \ From 4e03aa909580a9e29936dc2c4e2799d7cadc49b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Thu, 18 Jul 2024 16:43:42 +0000 Subject: [PATCH 033/115] Error if git-annex is enabled but missing (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copied from https://github.com/neuropoly/gitea/pull/47 This adds a check so that if `setting.Annex.Enabled` is true and git-annex is not in the PATH Forgejo will abort on startup with a reasonable error message. Fixes #15. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/16 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- cmd/web.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/web.go b/cmd/web.go index ecc53698e3..4e89153ab4 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -258,6 +259,12 @@ func runWeb(ctx *cli.Context) error { createPIDFile(ctx.String("pid")) } + if setting.Annex.Enabled { + if _, err := exec.LookPath("git-annex"); err != nil { + log.Fatal("You have enabled git-annex support but git-annex is not installed. Please make sure that Forgejo's PATH contains the git-annex executable.") + } + } + if !setting.InstallLock { if err := serveInstall(ctx); err != nil { return err From 88095d85ea0d3722dd37fb705bf720c3f66397ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Thu, 18 Jul 2024 18:18:06 +0000 Subject: [PATCH 034/115] Git-annex web uploads (#21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implements support for uploading files into the annex using the web interface. If a repository is a git-annex-enabled repository all files will be added to it using git annex add. This means that the repository's configuration for what to put into the annex (annex.largefiles in gitattributes) will be respected. Plain git repositories without git-annex will work as before, directly uploading to git. Fixes #5. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/21 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 6 ++ modules/util/remove.go | 11 ++-- services/repository/files/temp_repo.go | 20 +++++++ services/repository/files/upload.go | 83 +++++++++++++++++++++++++- tests/integration/git_annex_test.go | 81 +++++++++++++++++++++++++ 5 files changed, 194 insertions(+), 7 deletions(-) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 96b604693e..01d7743421 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -152,3 +152,9 @@ func IsAnnexed(blob *git.Blob) (bool, error) { } return true, nil } + +// IsAnnexRepo determines if repo is a git-annex enabled repository +func IsAnnexRepo(repo *git.Repository) bool { + _, _, err := git.NewCommand(repo.Ctx, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repo.Path}) + return err == nil +} diff --git a/modules/util/remove.go b/modules/util/remove.go index 4e5271136d..b07a48bee4 100644 --- a/modules/util/remove.go +++ b/modules/util/remove.go @@ -44,10 +44,13 @@ func MakeWritable(name string) error { return err } - // 0200 == u+w, in octal unix permission notation - err = os.Chmod(path, info.Mode()|0o200) - if err != nil { - return err + // Don't try chmod'ing symlinks (will fail with broken symlinks) + if info.Mode()&os.ModeSymlink != os.ModeSymlink { + // 0200 == u+w, in octal unix permission notation + err = os.Chmod(path, info.Mode()|0o200) + if err != nil { + return err + } } } return nil diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index b3aadbc6cb..5840fd6a4f 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -202,6 +202,26 @@ func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPat return nil } +// InitPrivateAnnex initializes a private annex in the repository +func (t *TemporaryUploadRepository) InitPrivateAnnex() error { + if _, _, err := git.NewCommand(t.ctx, "config", "annex.private", "true").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { + return err + } + if _, _, err := git.NewCommand(t.ctx, "annex", "init").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { + return err + } + return nil +} + +// AddAnnex adds the file at path to the repository using git annex add +// This requires a non-bare repository +func (t *TemporaryUploadRepository) AddAnnex(path string) error { + if _, _, err := git.NewCommand(t.ctx, "annex", "add").AddDynamicArguments(path).RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { + return err + } + return nil +} + // WriteTree writes the current index as a tree to the object db and returns its hash func (t *TemporaryUploadRepository) WriteTree() (string, error) { stdout, _, err := git.NewCommand(t.ctx, "write-tree").RunStdString(&git.RunOpts{Dir: t.basePath}) diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index 6359087e88..39a3f1d74d 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -6,13 +6,16 @@ package files import ( "context" "fmt" + "io" "os" "path" + "path/filepath" "strings" git_model "forgejo.org/models/git" repo_model "forgejo.org/models/repo" user_model "forgejo.org/models/user" + "forgejo.org/modules/annex" "forgejo.org/modules/git" "forgejo.org/modules/lfs" "forgejo.org/modules/setting" @@ -89,7 +92,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use defer t.Close() hasOldBranch := true - if err = t.Clone(opts.OldBranch, true); err != nil { + if err = t.Clone(opts.OldBranch, false); err != nil { if !git.IsErrBranchNotExist(err) || !repo.IsEmpty { return err } @@ -105,10 +108,30 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } } - // Copy uploaded files into repository. - if err := copyUploadedLFSFilesIntoRepository(infos, t, opts.TreePath); err != nil { + r, err := git.OpenRepository(ctx, repo.RepoPath()) + if err != nil { return err } + if annex.IsAnnexRepo(r) { + // Initialize annex privately in temporary clone + if err := t.InitPrivateAnnex(); err != nil { + return err + } + // Copy uploaded files into git-annex repository + if err := copyUploadedFilesIntoAnnexRepository(infos, t, opts.TreePath); err != nil { + return err + } + // Move all annexed content in the temporary repository, i.e. everything we have just added, to the origin + author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer) + if err := moveAnnexedFilesToOrigin(t, author, committer); err != nil { + return err + } + } else { + // Copy uploaded files into repository. + if err := copyUploadedLFSFilesIntoRepository(infos, t, opts.TreePath); err != nil { + return err + } + } // Now write the tree treeHash, err := t.WriteTree() @@ -246,3 +269,57 @@ func uploadToLFSContentStore(info uploadInfo, contentStore *lfs.ContentStore) er } return nil } + +func copyUploadedFilesIntoAnnexRepository(infos []uploadInfo, t *TemporaryUploadRepository, treePath string) error { + for i := range len(infos) { + if err := copyUploadedFileIntoAnnexRepository(&infos[i], t, treePath); err != nil { + return err + } + } + return nil +} + +func copyUploadedFileIntoAnnexRepository(info *uploadInfo, t *TemporaryUploadRepository, treePath string) error { + pathInRepo := path.Join(t.basePath, treePath, info.upload.Name) + if err := os.MkdirAll(filepath.Dir(pathInRepo), 0o700); err != nil { + return err + } + if err := os.Rename(info.upload.LocalPath(), pathInRepo); err != nil { + // Rename didn't work, try copy and remove + inputFile, err := os.Open(info.upload.LocalPath()) + if err != nil { + return fmt.Errorf("could not open source file: %v", err) + } + defer inputFile.Close() + outputFile, err := os.Create(pathInRepo) + if err != nil { + return fmt.Errorf("could not open dest file: %v", err) + } + defer outputFile.Close() + _, err = io.Copy(outputFile, inputFile) + if err != nil { + return fmt.Errorf("could not copy to dest from source: %v", err) + } + inputFile.Close() + err = os.Remove(info.upload.LocalPath()) + if err != nil { + return fmt.Errorf("could not remove source file: %v", err) + } + } + return t.AddAnnex(pathInRepo) +} + +func moveAnnexedFilesToOrigin(t *TemporaryUploadRepository, author, committer *user_model.User) error { + authorSig := author.NewGitSig() + committerSig := committer.NewGitSig() + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+authorSig.Name, + "GIT_AUTHOR_EMAIL="+authorSig.Email, + "GIT_COMMITTER_NAME="+committerSig.Name, + "GIT_COMMITTER_EMAIL="+committerSig.Email, + ) + if _, _, err := git.NewCommand(t.ctx, "annex", "move", "--to", "origin").RunStdString(&git.RunOpts{Dir: t.basePath, Env: env}); err != nil { + return err + } + return nil +} diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index db1bb8a498..b84ab46b2a 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -5,14 +5,17 @@ package integration import ( + "bytes" "errors" "fmt" "io" "math/rand" + "mime/multipart" "net/http" "net/url" "os" "path" + "path/filepath" "regexp" "strings" "testing" @@ -59,6 +62,84 @@ func doCreateRemoteAnnexRepository(t *testing.T, u *url.URL, ctx APITestContext, return nil } +func TestGitAnnexWebUpload(t *testing.T) { + if !setting.Annex.Enabled { + t.Skip("Skipping since annex support is disabled.") + } + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { + ctx := NewAPITestContext(t, "user2", "annex-web-upload-test"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository) + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false, objectFormat)) + + uploadFile := func(t *testing.T, path string) string { + t.Helper() + + body := &bytes.Buffer{} + mpForm := multipart.NewWriter(body) + err := mpForm.WriteField("_csrf", GetCSRF(t, ctx.Session, ctx.Username+"/"+ctx.Reponame+"/_upload/"+setting.Repository.DefaultBranch)) + require.NoError(t, err) + + file, err := mpForm.CreateFormFile("file", filepath.Base(path)) + require.NoError(t, err) + + srcFile, err := os.Open(path) + require.NoError(t, err) + + io.Copy(file, srcFile) + require.NoError(t, mpForm.Close()) + + req := NewRequestWithBody(t, "POST", "/"+ctx.Username+"/"+ctx.Reponame+"/upload-file", body) + req.Header.Add("Content-Type", mpForm.FormDataContentType()) + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + + respMap := map[string]string{} + DecodeJSON(t, resp, &respMap) + return respMap["uuid"] + } + + // Generate random file + tmpFile := path.Join(t.TempDir(), "web-upload-test-file.bin") + require.NoError(t, generateRandomFile(1024*1024/4, tmpFile)) + expectedContent, err := os.ReadFile(tmpFile) + require.NoError(t, err) + + // Upload generated file + fileUUID := uploadFile(t, tmpFile) + req := NewRequestWithValues(t, "POST", ctx.Username+"/"+ctx.Reponame+"/_upload/"+setting.Repository.DefaultBranch, map[string]string{ + "commit_choice": "direct", + "files": fileUUID, + "_csrf": GetCSRF(t, ctx.Session, ctx.Username+"/"+ctx.Reponame+"/_upload/"+setting.Repository.DefaultBranch), + "commit_mail_id": "-1", + }) + ctx.Session.MakeRequest(t, req, http.StatusSeeOther) + + // Get some handles on the target repository and file + remoteRepoPath := path.Join(setting.RepoRootPath, ctx.GitPath()) + repo, err := git.OpenRepository(git.DefaultContext, remoteRepoPath) + require.NoError(t, err) + defer repo.Close() + tree, err := repo.GetTree(setting.Repository.DefaultBranch) + require.NoError(t, err) + treeEntry, err := tree.GetTreeEntryByPath(filepath.Base(tmpFile)) + require.NoError(t, err) + blob := treeEntry.Blob() + + // Check that the uploaded file is annexed + isAnnexed, err := annex.IsAnnexed(blob) + require.NoError(t, err) + require.True(t, isAnnexed) + + // Check that the uploaded file has the correct content + annexedFile, err := annex.Content(blob) + require.NoError(t, err) + actualContent, err := io.ReadAll(annexedFile) + require.NoError(t, err) + require.Equal(t, expectedContent, actualContent) + }) + }) +} + func TestGitAnnexMedia(t *testing.T) { if !setting.Annex.Enabled { t.Skip("Skipping since annex support is disabled.") From 688f53d75217142cd2f5860553d63326e259ee3d Mon Sep 17 00:00:00 2001 From: Michael Hanke Date: Tue, 30 Jul 2024 13:15:26 +0000 Subject: [PATCH 035/115] Add git-annex also to the rootless container (#24) Same as https://codeberg.org/matrss/forgejo-aneksajo/commit/89f8aa0bf5134f7c986ea25856fa9ea32a0e0e8c, but for the rootless container. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/24 Reviewed-by: matrss Co-authored-by: Michael Hanke Co-committed-by: Michael Hanke --- Dockerfile.rootless | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.rootless b/Dockerfile.rootless index 47aae2798d..a6819c6cd2 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -73,6 +73,7 @@ RUN apk --no-cache add \ curl \ gnupg \ openssh-client \ + git-annex \ && rm -rf /var/cache/apk/* RUN addgroup \ From 0dc35349e4cdee1107bf43ce01db4d5f07ebe33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 9 Aug 2024 11:51:11 +0000 Subject: [PATCH 036/115] Improve views for annexed but missing files (#28) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, trying to view files that were annexed, but missing, just led to an uninformative error 500. This was rather confusing. With these changes it now shows the pointer target instead of the (missing) content of the file, and also indicates this situation in the "stored with git-annex" message. For semantic correctness views for missing files return a 404 instead of a 200, as they would with the content present. Fixes #7, fixes #13. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/28 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- options/locale/locale_de-DE.ini | 1 + options/locale/locale_en-US.ini | 1 + routers/web/repo/view.go | 54 +++++++++++++++++++++++---------- templates/repo/file_info.tmpl | 2 +- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 26f24ab6c7..bc012da26a 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1350,6 +1350,7 @@ video_not_supported_in_browser=Dein Browser unterstützt das HTML5-„video“-T audio_not_supported_in_browser=Dein Browser unterstützt das HTML5-„audio“-Tag nicht. stored_lfs=Gespeichert mit Git LFS stored_annex=Gespeichert mit Git Annex +stored_annex_not_present = hier nicht vorhanden, versuche git annex whereis symbolic_link=Softlink executable_file=Ausführbare Datei commit_graph=Commit-Graph diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5acf932e23..9ec018df5d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1368,6 +1368,7 @@ video_not_supported_in_browser = Your browser does not support the HTML5 "video" audio_not_supported_in_browser = Your browser does not support the HTML5 "audio" tag. stored_lfs = Stored with Git LFS stored_annex = Stored with Git Annex +stored_annex_not_present = not present here, try using git annex whereis symbolic_link = Symbolic link executable_file = Executable file vendored = Vendored diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 380218ec31..907e02bc3f 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -210,12 +210,13 @@ func localizedExtensions(ext, languageCode string) (localizedExts []string) { } type fileInfo struct { - isTextFile bool - isLFSFile bool - isAnnexFile bool - fileSize int64 - lfsMeta *lfs.Pointer - st typesniffer.SniffedType + isTextFile bool + isLFSFile bool + isAnnexFile bool + isAnnexFilePresent bool + fileSize int64 + lfsMeta *lfs.Pointer + st typesniffer.SniffedType } func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, io.ReadCloser, *fileInfo, error) { @@ -230,11 +231,22 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, annexContent, err := annex.Content(blob) if err != nil { - // in the case where annex content is missing, what should happen? - // do we render the page with an error message? - // actually that's not a bad idea, there's some sort of error message situation - // TODO: display an error to the user explaining that their data is missing - return nil, nil, nil, err + // If annex.Content returns an error it can mean that the blob does not + // refer to an annexed file or that it is not present here. Since we already + // checked that it is annexed the latter must be the case. So we return the + // content of the blob instead and indicate that the file is indeed annexed, + // but not present here. The template can then communicate the situation. + dataRc, err := blob.DataAsync() + if err != nil { + return nil, nil, nil, err + } + + buf := make([]byte, 1024) + n, _ := util.ReadAtMost(dataRc, buf) + buf = buf[:n] + + st := typesniffer.DetectContentType(buf) + return buf, dataRc, &fileInfo{st.IsText(), false, true, false, blob.Size(), nil, st}, nil } stat, err := annexContent.Stat() @@ -248,7 +260,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, st := typesniffer.DetectContentType(buf) - return buf, annexContent, &fileInfo{st.IsText(), false, true, stat.Size(), nil, st}, nil + return buf, annexContent, &fileInfo{st.IsText(), false, true, true, stat.Size(), nil, st}, nil } dataRc, err := blob.DataAsync() @@ -265,18 +277,18 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, // FIXME: what happens when README file is an image? if !isTextFile || !setting.LFS.StartServer { - return buf, dataRc, &fileInfo{isTextFile, false, false, blob.Size(), nil, st}, nil + return buf, dataRc, &fileInfo{isTextFile, false, false, false, blob.Size(), nil, st}, nil } pointer, _ := lfs.ReadPointerFromBuffer(buf) if !pointer.IsValid() { // fallback to plain file - return buf, dataRc, &fileInfo{isTextFile, false, false, blob.Size(), nil, st}, nil + return buf, dataRc, &fileInfo{isTextFile, false, false, false, blob.Size(), nil, st}, nil } meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid) if err != nil { // fallback to plain file log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err) - return buf, dataRc, &fileInfo{isTextFile, false, false, blob.Size(), nil, st}, nil + return buf, dataRc, &fileInfo{isTextFile, false, false, false, blob.Size(), nil, st}, nil } dataRc.Close() @@ -296,7 +308,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, st = typesniffer.DetectContentType(buf) - return buf, dataRc, &fileInfo{st.IsText(), true, false, meta.Size, &meta.Pointer, st}, nil + return buf, dataRc, &fileInfo{st.IsText(), true, false, false, meta.Size, &meta.Pointer, st}, nil } func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) { @@ -504,6 +516,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { } ctx.Data["IsLFSFile"] = fInfo.isLFSFile ctx.Data["IsAnnexFile"] = fInfo.isAnnexFile + ctx.Data["IsAnnexFilePresent"] = fInfo.isAnnexFilePresent ctx.Data["FileSize"] = fInfo.fileSize ctx.Data["IsTextFile"] = fInfo.isTextFile ctx.Data["IsRepresentableAsText"] = isRepresentableAsText @@ -1215,6 +1228,15 @@ PostRecentBranchCheck: } else { ctx.Data["CodeSearchOptions"] = git.GrepSearchOptions } + isAnnexFile, okAnnexFile := ctx.Data["IsAnnexFile"] + isAnnexFilePresent, okAnnexFilePresent := ctx.Data["IsAnnexFilePresent"] + if okAnnexFile && okAnnexFilePresent && isAnnexFile.(bool) && !isAnnexFilePresent.(bool) { + // If the file to be viewed is annexed but not present then render it normally + // (which will show the plain git blob content, i.e. the symlink or pointer target) + // but make the status code a 404. + ctx.HTML(http.StatusNotFound, tplRepoHome) + return + } ctx.HTML(http.StatusOK, tplRepoHome) } diff --git a/templates/repo/file_info.tmpl b/templates/repo/file_info.tmpl index 05d9825cfb..8655404394 100644 --- a/templates/repo/file_info.tmpl +++ b/templates/repo/file_info.tmpl @@ -17,7 +17,7 @@ {{if .FileSize}}
    {{ctx.Locale.TrSize .FileSize}}{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}} - {{if .IsAnnexFile}} ({{ctx.Locale.Tr "repo.stored_annex"}}){{end}} + {{if .IsAnnexFile}} ({{ctx.Locale.Tr "repo.stored_annex"}}{{if not .IsAnnexFilePresent}} - {{ctx.Locale.Tr "repo.stored_annex_not_present"}}{{end}}){{end}}
    {{end}} {{if .LFSLock}} From 8b50a576ec3fa25be5e12d5a47e6909701c5ff80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 9 Aug 2024 17:17:02 +0000 Subject: [PATCH 037/115] Change the icon for annexed files to file-binary (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #26. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/29 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/base/tool.go | 12 ++++++------ tests/integration/git_annex_test.go | 8 +++++--- web_src/css/repo.css | 1 + 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/modules/base/tool.go b/modules/base/tool.go index e70d7d4df7..38201c5919 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -102,14 +102,14 @@ func Int64sToStrings(ints []int64) []string { // EntryIcon returns the octicon class for displaying files/directories func EntryIcon(entry *git.TreeEntry) string { + isAnnexed, _ := annex.IsAnnexed(entry.Blob()) + if isAnnexed { + // Show git-annex files as binary files to differentiate them from non-annexed files + // TODO: find a more suitable icon, maybe something related to git-annex + return "file-binary" + } switch { case entry.IsLink(): - isAnnexed, _ := annex.IsAnnexed(entry.Blob()) - if isAnnexed { - // git-annex files are sometimes stored as symlinks; - // short-circuit that so like LFS they are displayed as regular files - return "file" - } te, _, err := entry.FollowLink() if err != nil { log.Debug(err.Error()) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index b84ab46b2a..90bdf0e258 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -214,7 +214,7 @@ func TestGitAnnexViews(t *testing.T) { session := loginUser(t, ctx.Username) t.Run("Index", func(t *testing.T) { - // test that annex symlinks renders with the _file icon_ on the main list + // test that annexed files render with the binary file icon on the main list defer tests.PrintCurrentTest(t)() repoLink := path.Join("/", ctx.Username, ctx.Reponame) @@ -222,8 +222,10 @@ func TestGitAnnexViews(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - isFileIcon := htmlDoc.Find("tr[data-entryname='annexed.tiff'] > td.name svg").HasClass("octicon-file") - require.True(t, isFileIcon, "annexed files should render a plain file icon, even when stored via annex symlink") + isFileBinaryIconLocked := htmlDoc.Find("tr[data-entryname='annexed.tiff'] > td.name svg").HasClass("octicon-file-binary") + require.True(t, isFileBinaryIconLocked, "locked annexed files should render a binary file icon") + isFileBinaryIconUnlocked := htmlDoc.Find("tr[data-entryname='annexed.bin'] > td.name svg").HasClass("octicon-file-binary") + require.True(t, isFileBinaryIconUnlocked, "unlocked annexed files should render a binary file icon") }) t.Run("View", func(t *testing.T) { diff --git a/web_src/css/repo.css b/web_src/css/repo.css index f498a992ed..c8049abe61 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -246,6 +246,7 @@ td .commit-summary { } .repository.file.list #repo-files-table tbody .svg.octicon-file, +.repository.file.list #repo-files-table tbody .svg.octicon-file-binary, .repository.file.list #repo-files-table tbody .svg.octicon-file-symlink-file, .repository.file.list #repo-files-table tbody .svg.octicon-file-directory-symlink { color: var(--color-secondary-dark-7); From 71338f38640ca4ee6b5cc948145620e47120f3d8 Mon Sep 17 00:00:00 2001 From: Michael Hanke Date: Wed, 4 Sep 2024 09:32:40 +0000 Subject: [PATCH 038/115] Elevate external markup renderer interface for annexed file content (#36) Previously, an external renderer that matched on an annexed file would only see its content streamed via `STDIN`, or a temporary file with a copy of its content would be generated and passed-by-filepath (with `IS_INPUT_FILE=true`). Whether that happens, is also subject to `MAX_DISPLAY_FILE_SIZE` (which defaults to 8MB). This was problematic, because annexed files tend to be large. Moreover, if present, they already exist as write-protected files on the file-system. Creating a copy is both expensive and serves no particular purpose. This commit changes how external renderers are called. 1) With `IS_INPUT_FILE=true`, the renderer is passed the true location of an annex key, if present, and an empty path, if not. 2) The original, repository-relative path of the rendering target is made available to the renderer via the `GITEA_RELATIVE_PATH` environment variable. To achieve a lean implementation, the `Blob` of the rendering target is passed on to the `RenderContext` (because the implementation of the annex-related functionality is centered on this dtype. This change makes it less costly to increase `MAX_DISPLAY_FILE_SIZE`, in order to make large, annexed files eligible for markup rendering, because no content copies will be made any longer. External renderers can now use the original file path, with the full original filename, including extensions, for decision making. For example, to detect particular compression formats based in a file name extension, or to alter the rendering based on contextual information encoded in the file path (e.g., a multi-file data structure with a particular organization pattern). Apart from the additional environment variable, there is no change to the handling of renderers that take their input via `STDIN` (i.e., `IS_INPUT_FILE=false`). Fixes #35. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/36 Reviewed-by: matrss Co-authored-by: Michael Hanke Co-committed-by: Michael Hanke --- modules/markup/external/external.go | 25 +++++++++++++++++++++++-- modules/markup/renderer.go | 20 ++++++++++++-------- routers/web/repo/view.go | 3 +++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index 87c1a284cc..950da6e828 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -11,6 +11,7 @@ import ( "os/exec" "strings" + "forgejo.org/modules/annex" "forgejo.org/modules/graceful" "forgejo.org/modules/log" "forgejo.org/modules/markup" @@ -82,8 +83,22 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. commands = strings.Fields(command) args = commands[1:] ) - - if p.IsInputFile { + isAnnexed, _ := annex.IsAnnexed(ctx.Blob) + // if a renderer wants to read a file, and we have annexed content, we can + // provide the annex key file location directly to the renderer. git-annex + // takes care of having that location be read-only, so no critical + // protection layer is needed. Moreover, the file readily exists, and + // expensive temporary files can be avoided, also allowing an operator + // to raise MAX_DISPLAY_FILE_SIZE without much negative impact. + if p.IsInputFile && isAnnexed { + // look for annexed content, will be empty, if there is none + annexContentLocation, _ := annex.ContentLocation(ctx.Blob) + // we call the renderer, even if there is no annex content present. + // showing the pointer file content is not much use, and a topical + // renderer might be able to produce something useful from the + // filename alone (present in ENV) + args = append(args, annexContentLocation) + } else if p.IsInputFile { // write to temp file f, err := os.CreateTemp("", "gitea_input") if err != nil { @@ -126,6 +141,12 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. os.Environ(), "GITEA_PREFIX_SRC="+ctx.Links.SrcLink(), "GITEA_PREFIX_RAW="+ctx.Links.RawLink(), + // also communicate the relative path of the to-be-rendered item. + // this enables the renderer to make use of the original file name + // and path, e.g., to make rendering or dtype-detection decisions + // that go beyond the originally matched extension. Even if the + // content is directly streamed to STDIN + "GITEA_RELATIVE_PATH="+ctx.RelativePath, ) if !p.IsInputFile { cmd.Stdin = input diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index a622d75085..8eec764bbe 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -67,14 +67,18 @@ type Header struct { // RenderContext represents a render context type RenderContext struct { - Ctx context.Context - RelativePath string // relative path from tree root of the branch - Type string - IsWiki bool - Links Links - Metas map[string]string - DefaultLink string - GitRepo *git.Repository + Ctx context.Context + RelativePath string // relative path from tree root of the branch + Type string + IsWiki bool + Links Links + Metas map[string]string + DefaultLink string + GitRepo *git.Repository + // reporting the target blob that is to-be-rendered enables + // deeper inspection in the handler for external renderer + // (i.e., more targeted handling of annexed files) + Blob *git.Blob ShaExistCache map[string]bool cancelFn func() SidebarTocNode ast.Node diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 907e02bc3f..c231b7ee6c 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -371,6 +371,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr }, Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx), GitRepo: ctx.Repo.GitRepo, + Blob: target.Blob(), }, rd) if err != nil { log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err) @@ -607,6 +608,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { }, Metas: metas, GitRepo: ctx.Repo.GitRepo, + Blob: entry.Blob(), }, rd) if err != nil { ctx.ServerError("Render", err) @@ -705,6 +707,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { }, Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx), GitRepo: ctx.Repo.GitRepo, + Blob: entry.Blob(), }, rd) if err != nil { ctx.ServerError("Render", err) From b41c692e04138430747b4f3c42dbd93497d4e9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Wed, 9 Oct 2024 14:44:18 +0000 Subject: [PATCH 039/115] Use PATH when looking for git commands (#44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes some issues when using a git-annex that is installed in a different location than where git is installed, e.g. when using the git-annex-standalone release or one installed with nix. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/44 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- cmd/serv.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/serv.go b/cmd/serv.go index 62e03dfd3b..f9e3bb8d4f 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -339,9 +339,8 @@ func runServ(c *cli.Context) error { return nil } - gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin - gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack - if _, err := os.Stat(gitBinVerb); err != nil { + gitBinVerb, err := exec.LookPath(verb) + if err != nil { // if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git // ps: Windows only has "git.exe" in the bin path, so Windows always uses this way // ps: git-annex-shell and other extensions may not necessarily be in gitBinPath, From 2aef6a880d963779bd226d1e757a7c4272523dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Wed, 9 Oct 2024 19:33:05 +0000 Subject: [PATCH 040/115] Only upload to annex in doAnnexUploadTest (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous implementation both uploaded to the annex and pushed to the git repository. This meant that the tests checking that uploads without permission fail actually could pass when the git push failed but the git-annex upload didn't. The tests didn't catch the situation where unauthorized users could modify the annex. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/46 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 23 +++++----- tests/integration/git_annex_test.go | 68 ++++++++++++++++------------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 01d7743421..e2101366eb 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -104,6 +104,18 @@ func Pointer(blob *git.Blob) (string, error) { return pointer, nil } +func ContentLocationFromPointer(repoPath, pointer string) (string, error) { + contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(pointer).RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return "", fmt.Errorf("in %s: %s does not seem to be a valid annexed file: %w", repoPath, pointer, err) + } + contentLocation = strings.TrimSpace(contentLocation) + contentLocation = path.Clean("/" + contentLocation)[1:] // prevent directory traversals + contentLocation = path.Join(repoPath, contentLocation) + + return contentLocation, nil +} + // return the absolute path of the content pointed to by the annex pointer stored in the git object // errors if the content is not found in this repo func ContentLocation(blob *git.Blob) (string, error) { @@ -111,16 +123,7 @@ func ContentLocation(blob *git.Blob) (string, error) { if err != nil { return "", err } - - contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(pointer).RunStdString(&git.RunOpts{Dir: blob.Repo().Path}) - if err != nil { - return "", fmt.Errorf("in %s: %s does not seem to be a valid annexed file: %w", blob.Repo().Path, pointer, err) - } - contentLocation = strings.TrimSpace(contentLocation) - contentLocation = path.Clean("/" + contentLocation)[1:] // prevent directory traversals - contentLocation = path.Join(blob.Repo().Path, contentLocation) - - return contentLocation, nil + return ContentLocationFromPointer(blob.Repo().Path, pointer) } // returns a stream open to the annex content diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 90bdf0e258..886b6dba63 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -1119,19 +1119,21 @@ func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { return err } - _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "sync", "--no-content").RunStdString(&git.RunOpts{Dir: repoPath}) - if err != nil { - return err - } - // verify the file was uploaded - localObjectPath, err := contentLocation(repoPath, "contribution.bin") + blob, err := blobForFile(repoPath, "contribution.bin") + if err != nil { + return err + } + key, err := annex.Pointer(blob) + if err != nil { + return err + } + localObjectPath, err := annex.ContentLocationFromPointer(repoPath, key) if err != nil { return err } - // localObjectPath := path.Join(repoPath, "contribution.bin") // or, just compare against the checked-out file - remoteObjectPath, err := contentLocation(remoteRepoPath, "contribution.bin") + remoteObjectPath, err := annex.ContentLocationFromPointer(remoteRepoPath, key) if err != nil { return err } @@ -1325,6 +1327,31 @@ func doInitRemoteAnnexRepository(t *testing.T, repoURL *url.URL) error { return nil } +func blobForFile(repoPath, file string) (*git.Blob, error) { + repo, err := git.OpenRepository(git.DefaultContext, repoPath) + if err != nil { + return nil, err + } + defer repo.Close() + + commitID, err := repo.GetRefCommitID("HEAD") // NB: to examine a *branch*, prefix with "refs/branch/", or call repo.GetBranchCommitID(); ditto for tags + if err != nil { + return nil, err + } + + commit, err := repo.GetCommit(commitID) + if err != nil { + return nil, err + } + + treeEntry, err := commit.GetTreeEntryByPath(file) + if err != nil { + return nil, err + } + + return treeEntry.Blob(), nil +} + /* Find the path in .git/annex/objects/ of the contents for a given annexed file. @@ -1334,30 +1361,11 @@ Find the path in .git/annex/objects/ of the contents for a given annexed file. TODO: pass a parameter to allow examining non-HEAD branches */ func contentLocation(repoPath, file string) (path string, err error) { - path = "" - - repo, err := git.OpenRepository(git.DefaultContext, repoPath) + blob, err := blobForFile(repoPath, file) if err != nil { - return path, nil + return "", err } - defer repo.Close() - - commitID, err := repo.GetRefCommitID("HEAD") // NB: to examine a *branch*, prefix with "refs/branch/", or call repo.GetBranchCommitID(); ditto for tags - if err != nil { - return path, nil - } - - commit, err := repo.GetCommit(commitID) - if err != nil { - return path, nil - } - - treeEntry, err := commit.GetTreeEntryByPath(file) - if err != nil { - return path, nil - } - - return annex.ContentLocation(treeEntry.Blob()) + return annex.ContentLocation(blob) } /* like withKeyFile(), but automatically sets it the account given in ctx for use by git-annex */ From be55d4233a15ca0a0cd68bfd7fdb3191c521a9c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Mon, 14 Oct 2024 15:59:21 +0000 Subject: [PATCH 041/115] Add git-annex' testremote to the test suite (#48) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `git annex testremote` command runs a built-in set of tests against a remote. It cannot hurt to check our implementation of a git-annex remote against it too. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/48 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- tests/integration/git_annex_test.go | 196 ++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 886b6dba63..c9c5d3cabf 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -395,6 +395,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -425,6 +435,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -457,6 +477,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -487,6 +517,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -519,6 +559,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -549,6 +599,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -581,6 +641,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -611,6 +681,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -642,6 +722,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) @@ -713,6 +803,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -743,6 +843,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -775,6 +885,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -805,6 +925,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -837,6 +967,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -867,6 +1007,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -899,6 +1049,16 @@ func TestGitAnnexPermissions(t *testing.T) { require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath), "annex copy --from should fail due to permissions") }) + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -929,6 +1089,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) }) @@ -960,6 +1130,16 @@ func TestGitAnnexPermissions(t *testing.T) { defer tests.PrintCurrentTest(t)() require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) }) }) @@ -1032,6 +1212,22 @@ func doAnnexInitTest(remoteRepoPath, repoPath string) (err error) { return nil } +func doAnnexTestremoteReadWriteTest(repoPath string) (err error) { + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "testremote", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + return nil +} + +func doAnnexTestremoteReadOnlyTest(repoPath string) (err error) { + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "testremote", "origin", "--test-readonly", "annexed.tiff").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + return nil +} + func doAnnexDownloadTest(remoteRepoPath, repoPath string) (err error) { // NB: this test does something slightly different if run separately from "doAnnexInitTest()": // "git annex copy" will notice and run "git annex init", silently. From 59510cd90d5d6aab3d94857c44ef0ac0a5a4e701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Tue, 22 Oct 2024 18:33:26 +0000 Subject: [PATCH 042/115] Add tests for git annex drop (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds some rudimentary tests that drop files in a repository's clone as well as from a repository on Forgejo. Fixes #4. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/47 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- tests/integration/git_annex_test.go | 1004 +++++++++++++++++++++------ 1 file changed, 796 insertions(+), 208 deletions(-) diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index c9c5d3cabf..1c12f3a032 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -382,34 +382,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, ownerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) - }) }) }) @@ -425,24 +451,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -464,34 +524,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, writerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) - }) }) }) @@ -507,24 +593,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, writerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -546,34 +666,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, readerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") - }) }) }) @@ -589,24 +735,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, readerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -628,34 +808,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, outsiderCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") - }) }) }) @@ -671,24 +877,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -723,6 +963,26 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + t.Run("TestremoteReadOnly", func(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) @@ -790,34 +1050,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, ownerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, ownerCtx, func() { require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) - }) }) }) @@ -833,24 +1119,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -872,34 +1192,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, writerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, writerCtx, func() { require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) - }) }) }) @@ -915,24 +1261,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, writerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -954,34 +1334,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, readerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, readerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "Uploading should fail due to permissions") - }) }) }) @@ -997,24 +1403,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, readerCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -1036,34 +1476,60 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxKeyFile(t, outsiderCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath), "annex init should fail due to permissions") }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath), "annex copy --from should fail due to permissions") }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "annex copy --to should fail due to permissions") + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "annex copy --to should fail due to permissions") - }) }) }) @@ -1079,24 +1545,58 @@ func TestGitAnnexPermissions(t *testing.T) { doGitClone(repoPath, repoURL)(t) }) - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + }) - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + }) - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) + }) - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) @@ -1131,6 +1631,26 @@ func TestGitAnnexPermissions(t *testing.T) { require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexLocalDropTest(repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + t.Run("TestremoteReadOnly", func(t *testing.T) { defer tests.PrintCurrentTest(t)() require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) @@ -1290,6 +1810,40 @@ func doAnnexDownloadTest(remoteRepoPath, repoPath string) (err error) { return err } +func doAnnexLocalDropTest(repoPath string) (err error) { + // This test assumes that files are present in repoPath, i.e. it is run after doAnnexDownloadTest. + // This test drops all files from the repository clone. + binPath, err := contentLocation(repoPath, "annexed.bin") + if err != nil { + return err + } + _, err = os.Stat(binPath) + if err != nil { + return err + } + tiffPath, err := contentLocation(repoPath, "annexed.tiff") + if err != nil { + return err + } + _, err = os.Stat(tiffPath) + if err != nil { + return err + } + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "drop").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + _, err = os.Stat(binPath) + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("annexed.bin wasn't dropped properly: %w", err) + } + _, err = os.Stat(tiffPath) + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("annexed.tiff wasn't dropped properly: %w", err) + } + return nil +} + func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { // NB: this test does something slightly different if run separately from "Init": // it first runs "git annex init" silently in the background. @@ -1345,6 +1899,40 @@ func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { return nil } +func doAnnexRemoteDropTest(remoteRepoPath, repoPath string) (err error) { + // This test assumes that files are present in repoPath, i.e. it is run after doAnnexDownloadTest. + // This test drops all files from the remote repository. + binPath, err := contentLocation(remoteRepoPath, "annexed.bin") + if err != nil { + return err + } + _, err = os.Stat(binPath) + if err != nil { + return err + } + tiffPath, err := contentLocation(remoteRepoPath, "annexed.tiff") + if err != nil { + return err + } + _, err = os.Stat(tiffPath) + if err != nil { + return err + } + _, _, err = git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "drop", "--from", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return err + } + _, err = os.Stat(binPath) + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("annexed.bin wasn't dropped properly: %w", err) + } + _, err = os.Stat(tiffPath) + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("annexed.tiff wasn't dropped properly: %w", err) + } + return nil +} + // ---- Helpers ---- func generateRandomFile(size int, path string) (err error) { From eba1a9626375494f5232ffdb84d284d21ab91a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 25 Oct 2024 09:55:56 +0000 Subject: [PATCH 043/115] Add git-annex p2phttp support (#42) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new endpoint under `/git-annex-p2phttp` which acts as an authenticating proxy to git-annex' p2phttp server. This makes it possible to set `annex+/git-annex-p2phttp` as `remote..annexurl` and use git-annex fully over http(s) with the normal credentials and access tokens provided by Forgejo. Fixes #25. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/42 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- .forgejo/workflows/testing.yml | 3 - custom/conf/app.example.ini | 2 + modules/annex/annex.go | 28 + modules/setting/annex.go | 7 +- routers/web/repo/annex.go | 146 +++++ routers/web/repo/githttp.go | 20 + routers/web/web.go | 12 +- services/auth/auth.go | 2 +- tests/integration/git_annex_test.go | 888 +++++++++++++++++++++++++--- 9 files changed, 1012 insertions(+), 96 deletions(-) create mode 100644 routers/web/repo/annex.go diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 7e3a951872..713dfebf63 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -200,7 +200,6 @@ jobs: - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-mysql-migration test-mysql' - timeout-minutes: 120 env: USE_REPO_TEST_DIR: 1 test-pgsql: @@ -236,7 +235,6 @@ jobs: - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-pgsql-migration test-pgsql' - timeout-minutes: 120 env: RACE_ENABLED: true USE_REPO_TEST_DIR: 1 @@ -257,7 +255,6 @@ jobs: - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-sqlite-migration test-sqlite' - timeout-minutes: 120 env: TAGS: sqlite sqlite_unlock_notify RACE_ENABLED: true diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 789178d9be..b76cf7df80 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2686,6 +2686,8 @@ LEVEL = Info ;; ;; Whether git-annex is enabled; defaults to false ;ENABLED = false +;; Whether to disable p2phttp support; default is the same as repository.DISABLE_HTTP_GIT +;DISABLE_P2PHTTP = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/annex/annex.go b/modules/annex/annex.go index e2101366eb..016f8ca8fb 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -12,8 +12,11 @@ package annex import ( "errors" "fmt" + "io/fs" "os" "path" + "path/filepath" + "regexp" "strings" "forgejo.org/modules/git" @@ -161,3 +164,28 @@ func IsAnnexRepo(repo *git.Repository) bool { _, _, err := git.NewCommand(repo.Ctx, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repo.Path}) return err == nil } + +var repoConfigFileRe = regexp.MustCompile("[^/]+/[^/]+.git/config$") + +func UUID2RepoPath(uuid string) (string, error) { + var repoPath string + err := filepath.WalkDir(setting.RepoRootPath, func(path string, d fs.DirEntry, err error) error { + if err == nil && repoConfigFileRe.MatchString(path) { + thisRepoPath := strings.TrimSuffix(path, "/config") + stdout, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: thisRepoPath}) + if err != nil { + return nil + } + repoUUID := strings.TrimSpace(stdout) + if repoUUID == uuid { + repoPath = thisRepoPath + return fs.SkipAll + } + } + return nil + }) + if err != nil { + return "", err + } + return repoPath, nil +} diff --git a/modules/setting/annex.go b/modules/setting/annex.go index b8616ba87d..aa41c14ff0 100644 --- a/modules/setting/annex.go +++ b/modules/setting/annex.go @@ -9,7 +9,8 @@ import ( // Annex represents the configuration for git-annex var Annex = struct { - Enabled bool `ini:"ENABLED"` + Enabled bool `ini:"ENABLED"` + DisableP2PHTTP bool `ini:"DISABLE_P2PHTTP"` }{} func loadAnnexFrom(rootCfg ConfigProvider) { @@ -17,4 +18,8 @@ func loadAnnexFrom(rootCfg ConfigProvider) { if err := sec.MapTo(&Annex); err != nil { log.Fatal("Failed to map Annex settings: %v", err) } + if !sec.HasKey("DISABLE_P2PHTTP") { + // If DisableP2PHTTP is not explicitly set then use DisableHTTPGit as its default + Annex.DisableP2PHTTP = Repository.DisableHTTPGit + } } diff --git a/routers/web/repo/annex.go b/routers/web/repo/annex.go new file mode 100644 index 0000000000..facdded335 --- /dev/null +++ b/routers/web/repo/annex.go @@ -0,0 +1,146 @@ +package repo + +import ( + "context" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "os/exec" + "strings" + "syscall" + "time" + + "forgejo.org/models/perm" + access_model "forgejo.org/models/perm/access" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unit" + "forgejo.org/modules/annex" + "forgejo.org/modules/graceful" + "forgejo.org/modules/log" + services_context "forgejo.org/services/context" +) + +type p2phttpRecordType struct { + CancelFunc func() + LastUsed time.Time + Port string +} + +var p2phttpRecords = make(map[string]*p2phttpRecordType) + +// AnnexP2PHTTP implements git-annex smart HTTP support by delegating to git annex p2phttp +func AnnexP2PHTTP(ctx *services_context.Context) { + uuid := ctx.Params(":uuid") + repoPath, err := annex.UUID2RepoPath(uuid) + if err != nil { + ctx.PlainText(http.StatusNotFound, "Repository not found") + return + } + + parts := strings.Split(repoPath, "/") + repoName := strings.TrimSuffix(parts[len(parts)-1], ".git") + owner := parts[len(parts)-2] + repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, repoName) + if err != nil { + ctx.PlainText(http.StatusNotFound, "Repository not found") + return + } + + p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } + + if !(ctx.Req.Method == "GET" && p.CanAccess(perm.AccessModeRead, unit.TypeCode) || + ctx.Req.Method == "POST" && p.CanAccess(perm.AccessModeWrite, unit.TypeCode) || + ctx.Req.Method == "POST" && strings.HasSuffix(ctx.Req.URL.Path, "/checkpresent") && p.CanAccess(perm.AccessModeRead, unit.TypeCode) || + ctx.Req.Method == "POST" && strings.HasSuffix(ctx.Req.URL.Path, "/keeplocked") || + ctx.Req.Method == "POST" && strings.HasSuffix(ctx.Req.URL.Path, "/lockcontent")) { + // GET requests require at least read access; POST requests for + // anything but checkpresent, lockcontent, and keeplocked + // require write permissions; POST requests for checkpresent + // only require read permissions, as it really is just a read. + // POST requests for lockcontent and keeplocked require no + // authentication at all, as is also the case for the + // authentication in the git-annex-p2phttp server. See + // https://git-annex.branchable.com/bugs/p2phttp__58___drop_difference_wideopen_unauth-readonly/ + // for reasoning. + ctx.Resp.WriteHeader(http.StatusUnauthorized) + return + } + + p2phttpRecord, p2phttpProcessExists := p2phttpRecords[uuid] + if p2phttpProcessExists { + p2phttpRecord.LastUsed = time.Now() + } else { + // Start a new p2phttp process for the requested repository + // There is a race condition here with the port selection, ideally git annex p2phttp could just listen on a unix socket... + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Error("Failed to listen on a free port: %v", err) + ctx.Resp.WriteHeader(http.StatusInternalServerError) + return + } + hopefullyFreePort := strings.SplitN(lis.Addr().String(), ":", 2)[1] + lis.Close() + p2phttpCtx, p2phttpCtxCancel := context.WithCancel(context.Background()) + go func(ctx context.Context) { + cmd := exec.CommandContext(ctx, "git", "-C", repoPath, "annex", "p2phttp", "-J2", "--bind", "127.0.0.1", "--wideopen", "--port", hopefullyFreePort) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGINT, + } + cmd.Cancel = func() error { return cmd.Process.Signal(os.Interrupt) } + _ = cmd.Run() + }(p2phttpCtx) + graceful.GetManager().RunAtTerminate(p2phttpCtxCancel) + + // Wait for the p2phttp server to get ready + start := time.Now() + sleepDuration := 1 * time.Millisecond + for { + if time.Since(start) > 5*time.Second { + p2phttpCtxCancel() + log.Error("Failed to start the p2phttp server in a reasonable amount of time") + ctx.Resp.WriteHeader(http.StatusInternalServerError) + return + } + conn, err := net.Dial("tcp", "127.0.0.1:"+hopefullyFreePort) + if err == nil { + conn.Close() + break + } + time.Sleep(sleepDuration) + sleepDuration *= 2 + if sleepDuration > 1*time.Second { + sleepDuration = 1 * time.Second + } + } + + p2phttpRecord = &p2phttpRecordType{CancelFunc: p2phttpCtxCancel, LastUsed: time.Now(), Port: hopefullyFreePort} + p2phttpRecords[uuid] = p2phttpRecord + } + + // Cleanup p2phttp processes that haven't been used for a while + for uuid, record := range p2phttpRecords { + if time.Since(record.LastUsed) > 5*time.Minute { + record.CancelFunc() + delete(p2phttpRecords, uuid) + } + } + + url, err := url.Parse("http://127.0.0.1:" + p2phttpRecord.Port + strings.TrimPrefix(ctx.Req.RequestURI, "/git-annex-p2phttp")) + if err != nil { + log.Error("Failed to parse URL: %v", err) + ctx.Resp.WriteHeader(http.StatusInternalServerError) + return + } + proxy := httputil.ReverseProxy{ + Rewrite: func(r *httputil.ProxyRequest) { + r.Out.URL = url + }, + } + proxy.ServeHTTP(ctx.Resp, ctx.Req) +} diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 3c95f4bd4c..3e80e120b5 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -545,6 +545,26 @@ func GetInfoRefs(ctx *context.Context) { } } +// GetConfig implements fetching the git config of a repository +func GetConfig(ctx *context.Context) { + h := httpBase(ctx) + if h != nil { + setHeaderNoCache(ctx) + config, err := os.ReadFile(filepath.Join(h.getRepoDir(), "config")) + if err != nil { + log.Error("Failed to read git config file: %v", err) + ctx.Resp.WriteHeader(http.StatusInternalServerError) + return + } + if !setting.Annex.DisableP2PHTTP { + config = append(config, []byte("[annex]\n\turl = annex+"+setting.AppURL+"git-annex-p2phttp\n")...) + } + ctx.Resp.Header().Set("Content-Type", "text/plain") + ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(config))) + http.ServeContent(ctx.Resp, ctx.Req, "config", time.Now(), bytes.NewReader(config)) + } +} + // GetTextFile implements Git dumb HTTP func GetTextFile(p string) func(*context.Context) { return func(ctx *context.Context) { diff --git a/routers/web/web.go b/routers/web/web.go index 91400d5d17..2840ee419a 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -357,6 +357,13 @@ func registerRoutes(m *web.Route) { } } + annexP2PHTTPEnabled := func(ctx *context.Context) { + if setting.Annex.DisableP2PHTTP { + ctx.Error(http.StatusNotFound) + return + } + } + federationEnabled := func(ctx *context.Context) { if !setting.Federation.Enabled { ctx.Error(http.StatusNotFound) @@ -959,6 +966,9 @@ func registerRoutes(m *web.Route) { // ***** END: Organization ***** // ***** START: Repository ***** + m.Group("", func() { + m.Methods("GET,POST", "/git-annex-p2phttp/git-annex/{uuid}/*", repo.AnnexP2PHTTP) + }, ignSignInAndCsrf, annexEnabled, annexP2PHTTPEnabled) m.Group("/repo", func() { m.Get("/create", repo.Create) m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost) @@ -1641,7 +1651,7 @@ func registerRoutes(m *web.Route) { m.Group("", func() { // for git-annex - m.Methods("GET,OPTIONS", "/config", repo.GetTextFile("config")) // needed by clients reading annex.uuid during `git annex initremote` + m.Methods("GET,OPTIONS", "/config", repo.GetConfig) // needed by clients reading annex.uuid during `git annex initremote` m.Methods("GET,OPTIONS", "/annex/objects/{hash1}/{hash2}/{keyDir}/{key}", repo.GetAnnexObject) }, ignSignInAndCsrf, annexEnabled, context.UserAssignmentWeb()) diff --git a/services/auth/auth.go b/services/auth/auth.go index 9d72a88c1f..d0fa725854 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -61,7 +61,7 @@ func isArchivePath(req *http.Request) bool { return archivePathRe.MatchString(req.URL.Path) } -var annexPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/annex/`) +var annexPathRe = regexp.MustCompile(`^(/git-annex-p2phttp/|/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/annex/)`) func isAnnexPath(req *http.Request) bool { if setting.Annex.Enabled { diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 1c12f3a032..d09dc558a2 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -458,6 +458,10 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { @@ -507,6 +511,75 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) }) t.Run("Writer", func(t *testing.T) { @@ -600,6 +673,10 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() withAnnexCtxHTTPPassword(t, u, writerCtx, func() { @@ -649,6 +726,75 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) }) t.Run("Reader", func(t *testing.T) { @@ -742,6 +888,79 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() withAnnexCtxHTTPPassword(t, u, readerCtx, func() { @@ -884,6 +1103,79 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err = git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { @@ -938,7 +1230,7 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("Anonymous", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - // Only HTTP has an anonymous mode + // Only HTTP and P2PHTTP have an anonymous mode t.Run("HTTP", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -958,6 +1250,65 @@ func TestGitAnnexPermissions(t *testing.T) { require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + // unlike the other tests, at this step we *do not* define credentials: + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) @@ -1126,6 +1477,10 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { @@ -1175,6 +1530,75 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) }) t.Run("Writer", func(t *testing.T) { @@ -1268,6 +1692,10 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() withAnnexCtxHTTPPassword(t, u, writerCtx, func() { @@ -1317,6 +1745,75 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, writerCtx, func() { + require.NoError(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) }) t.Run("Reader", func(t *testing.T) { @@ -1410,6 +1907,10 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) + // Unset annexurl so that git-annex uses the dumb http support + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.NoError(t, err) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() withAnnexCtxHTTPPassword(t, u, readerCtx, func() { @@ -1459,81 +1960,8 @@ func TestGitAnnexPermissions(t *testing.T) { }) }) }) - }) - t.Run("Outsider", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("SSH", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - repoURL := createSSHUrl(ownerCtx.GitPath(), u) - - repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) - defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions - - withAnnexCtxKeyFile(t, ownerCtx, func() { - doGitClone(repoPath, repoURL)(t) - }) - - t.Run("Init", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath), "annex init should fail due to permissions") - }) - }) - - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath), "annex copy --from should fail due to permissions") - }) - }) - - t.Run("LocalDrop", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexLocalDropTest(repoPath)) - }) - }) - - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) - }) - }) - - t.Run("RemoteDrop", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) - }) - }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "annex copy --to should fail due to permissions") - }) - }) - - t.Run("TestremoteReadOnly", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) - }) - }) - - t.Run("TestremoteReadWrite", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - withAnnexCtxKeyFile(t, outsiderCtx, func() { - require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) - }) - }) - }) - - t.Run("HTTP", func(t *testing.T) { + t.Run("P2PHTTP", func(t *testing.T) { defer tests.PrintCurrentTest(t)() repoURL := createHTTPUrl(ownerCtx.GitPath(), u) @@ -1547,66 +1975,281 @@ func TestGitAnnexPermissions(t *testing.T) { t.Run("Init", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { - require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) }) t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { - require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) }) t.Run("LocalDrop", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { - require.Error(t, doAnnexLocalDropTest(repoPath)) + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexLocalDropTest(repoPath)) }) }) t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { - require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) }) }) t.Run("RemoteDrop", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) }) }) t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) }) }) t.Run("TestremoteReadOnly", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { - require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { + require.NoError(t, doAnnexTestremoteReadOnlyTest(repoPath)) }) }) t.Run("TestremoteReadWrite", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + withAnnexCtxHTTPPassword(t, u, readerCtx, func() { require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) }) }) }) + + t.Run("Outsider", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("SSH", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createSSHUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxKeyFile(t, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath), "annex init should fail due to permissions") + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath), "annex copy --from should fail due to permissions") + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath), "annex copy --to should fail due to permissions") + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxKeyFile(t, outsiderCtx, func() { + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) + + t.Run("HTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + + // Try unsetting annexurl + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.Error(t, err) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexLocalDropTest(repoPath)) + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + withAnnexCtxHTTPPassword(t, u, outsiderCtx, func() { + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + }) + }) }) t.Run("Anonymous", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - // Only HTTP has an anonymous mode + // Only HTTP and P2PHTTP have an anonymous mode t.Run("HTTP", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -1626,6 +2269,65 @@ func TestGitAnnexPermissions(t *testing.T) { require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) }) + // Try unsetting annexurl + _, _, err := git.NewCommand(git.DefaultContext, "config", "--unset", "remote.origin.annexurl").RunStdString(&git.RunOpts{Dir: repoPath}) + require.Error(t, err) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("LocalDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexLocalDropTest(repoPath)) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) + }) + + t.Run("RemoteDrop", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexRemoteDropTest(remoteRepoPath, repoPath)) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexUploadTest(remoteRepoPath, repoPath)) + }) + + t.Run("TestremoteReadOnly", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadOnlyTest(repoPath)) + }) + + t.Run("TestremoteReadWrite", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexTestremoteReadWriteTest(repoPath)) + }) + }) + + t.Run("P2PHTTP", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repoURL := createHTTPUrl(ownerCtx.GitPath(), u) + + repoPath := path.Join(t.TempDir(), ownerCtx.Reponame) + defer util.RemoveAll(repoPath) // cleans out git-annex lockdown permissions + + withAnnexCtxHTTPPassword(t, u, ownerCtx, func() { + doGitClone(repoPath, repoURL)(t) + }) + + // unlike the other tests, at this step we *do not* define credentials: + + t.Run("Init", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + require.Error(t, doAnnexInitTest(remoteRepoPath, repoPath)) + }) + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() require.Error(t, doAnnexDownloadTest(remoteRepoPath, repoPath)) @@ -2189,8 +2891,14 @@ func withAnnexCtxHTTPPassword(t *testing.T, u *url.URL, ctx APITestContext, call credentialedURL := *u credentialedURL.User = url.UserPassword(ctx.Username, userPassword) // NB: all test users use the same password + credentialedAnnexURL := *u + credentialedAnnexURL.Host = strings.ReplaceAll(credentialedAnnexURL.Host, "127.0.0.1", "localhost") + credentialedAnnexURL.Scheme = "annex+" + credentialedAnnexURL.Scheme + credentialedAnnexURL.Path += "git-annex-p2phttp" + credentialedAnnexURL.User = url.UserPassword(ctx.Username, userPassword) // NB: all test users use the same password + creds := path.Join(t.TempDir(), "creds") - require.NoError(t, os.WriteFile(creds, []byte(credentialedURL.String()), 0o600)) + require.NoError(t, os.WriteFile(creds, []byte(credentialedURL.String()+"\n"+credentialedAnnexURL.String()+"\n"), 0o600)) originalCredentialHelper, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "config").AddOptionValues("--global", "credential.helper").RunStdString(&git.RunOpts{}) if err != nil && !git.IsErrorExitCode(err, 1) { From 14c0633698b03a045c669b6f5bbd0f50bfa0762b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 25 Oct 2024 09:56:36 +0000 Subject: [PATCH 044/115] Simplify git blob to annex key lookup (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #27. Reviewed-on: https://codeberg.org/matrss/forgejo-aneksajo/pulls/43 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 102 ++++------------------------ tests/integration/git_annex_test.go | 6 +- 2 files changed, 18 insertions(+), 90 deletions(-) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 016f8ca8fb..5d239f7752 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -21,96 +21,24 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/setting" - "forgejo.org/modules/util" ) -const ( - // > The maximum size of a pointer file is 32 kb. - // - https://git-annex.branchable.com/internals/pointer_file/ - // It's unclear if that's kilobytes or kibibytes; assuming kibibytes: - blobSizeCutoff = 32 * 1024 -) +// ErrBlobIsNotAnnexed occurs if a blob does not contain a valid annex key +var ErrBlobIsNotAnnexed = errors.New("not a git-annex pointer") -// ErrInvalidPointer occurs if the pointer's value doesn't parse -var ErrInvalidPointer = errors.New("Not a git-annex pointer") - -// Gets the content of the blob as raw text, up to n bytes. -// (the pre-existing blob.GetBlobContent() has a hardcoded 1024-byte limit) -func getBlobContent(b *git.Blob, n int) (string, error) { - dataRc, err := b.DataAsync() +func LookupKey(blob *git.Blob) (string, error) { + stdout, _, err := git.NewCommand(git.DefaultContext, "annex", "lookupkey", "--ref").AddDynamicArguments(blob.ID.String()).RunStdString(&git.RunOpts{Dir: blob.Repo().Path}) if err != nil { - return "", err + return "", ErrBlobIsNotAnnexed } - defer dataRc.Close() - buf := make([]byte, n) - n, _ = util.ReadAtMost(dataRc, buf) - buf = buf[:n] - return string(buf), nil + key := strings.TrimSpace(stdout) + return key, nil } -func Pointer(blob *git.Blob) (string, error) { - // git-annex doesn't seem fully spec what its pointer are, but - // the fullest description is here: - // https://git-annex.branchable.com/internals/pointer_file/ - - // a pointer can be: - // the original format, generated by `git annex add`: a symlink to '.git/annex/objects/$HASHDIR/$HASHDIR2/$KEY/$KEY' - // the newer, git-lfs influenced, format, generated by `git annex smudge`: a text file containing '/annex/objects/$KEY' - // - // in either case we can extract the $KEY the same way, and we need not actually know if it's a symlink or not because - // git.Blob.DataAsync() works like open() + readlink(), handling both cases in one. - - if blob.Size() > blobSizeCutoff { - // > The maximum size of a pointer file is 32 kb. If it is any longer, it is not considered to be a valid pointer file. - // https://git-annex.branchable.com/internals/pointer_file/ - - // It's unclear to me whether the same size limit applies to symlink-pointers, but it seems sensible to limit them too. - return "", ErrInvalidPointer - } - - pointer, err := getBlobContent(blob, blobSizeCutoff) +func ContentLocationFromKey(repoPath, key string) (string, error) { + contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(key).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { - return "", fmt.Errorf("error reading %s: %w", blob.Name(), err) - } - - // the spec says a pointer file can contain multiple lines each with a pointer in them - // but that makes no sense to me, so I'm just ignoring all but the first - lines := strings.Split(pointer, "\n") - if len(lines) < 1 { - return "", ErrInvalidPointer - } - pointer = lines[0] - - // in both the symlink and pointer-file formats, the pointer must have "/annex/" somewhere in it - if !strings.Contains(pointer, "/annex/") { - return "", ErrInvalidPointer - } - - // extract $KEY - pointer = path.Base(strings.TrimSpace(pointer)) - - // ask git-annex's opinion on $KEY - // XXX: this is probably a bit slow, especially if this operation gets run often - // and examinekey is not that strict: - // - it doesn't enforce that the "BACKEND" tag is one it knows, - // - it doesn't enforce that the fields and their format fit the "BACKEND" tag - // so maybe this is a wasteful step - _, examineStderr, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "examinekey").AddDynamicArguments(pointer).RunStdString(&git.RunOpts{Dir: blob.Repo().Path}) - if err != nil { - // TODO: make ErrInvalidPointer into a type capable of wrapping err - if strings.TrimSpace(examineStderr) == "git-annex: bad key" { - return "", ErrInvalidPointer - } - return "", err - } - - return pointer, nil -} - -func ContentLocationFromPointer(repoPath, pointer string) (string, error) { - contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(pointer).RunStdString(&git.RunOpts{Dir: repoPath}) - if err != nil { - return "", fmt.Errorf("in %s: %s does not seem to be a valid annexed file: %w", repoPath, pointer, err) + return "", fmt.Errorf("in %s: %s does not seem to be a valid annexed file: %w", repoPath, key, err) } contentLocation = strings.TrimSpace(contentLocation) contentLocation = path.Clean("/" + contentLocation)[1:] // prevent directory traversals @@ -122,11 +50,11 @@ func ContentLocationFromPointer(repoPath, pointer string) (string, error) { // return the absolute path of the content pointed to by the annex pointer stored in the git object // errors if the content is not found in this repo func ContentLocation(blob *git.Blob) (string, error) { - pointer, err := Pointer(blob) + key, err := LookupKey(blob) if err != nil { return "", err } - return ContentLocationFromPointer(blob.Repo().Path, pointer) + return ContentLocationFromKey(blob.Repo().Path, key) } // returns a stream open to the annex content @@ -147,11 +75,11 @@ func IsAnnexed(blob *git.Blob) (bool, error) { return false, nil } - // Pointer() is written to only return well-formed pointers + // LookupKey is written to only return well-formed keys // so the test is just to see if it errors - _, err := Pointer(blob) + _, err := LookupKey(blob) if err != nil { - if errors.Is(err, ErrInvalidPointer) { + if errors.Is(err, ErrBlobIsNotAnnexed) { return false, nil } return false, err diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index d09dc558a2..14792af066 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -2576,16 +2576,16 @@ func doAnnexUploadTest(remoteRepoPath, repoPath string) (err error) { if err != nil { return err } - key, err := annex.Pointer(blob) + key, err := annex.LookupKey(blob) if err != nil { return err } - localObjectPath, err := annex.ContentLocationFromPointer(repoPath, key) + localObjectPath, err := annex.ContentLocationFromKey(repoPath, key) if err != nil { return err } - remoteObjectPath, err := annex.ContentLocationFromPointer(remoteRepoPath, key) + remoteObjectPath, err := annex.ContentLocationFromKey(remoteRepoPath, key) if err != nil { return err } From 5b3d77e3b2c6849980b1bd2bd860edec38fd581c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Tue, 29 Oct 2024 11:45:40 +0000 Subject: [PATCH 045/115] Add an OCI image build and publish workflow (#50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #49. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/50 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- .forgejo/workflows/build-oci-image.yml | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .forgejo/workflows/build-oci-image.yml diff --git a/.forgejo/workflows/build-oci-image.yml b/.forgejo/workflows/build-oci-image.yml new file mode 100644 index 0000000000..9eabdf3eab --- /dev/null +++ b/.forgejo/workflows/build-oci-image.yml @@ -0,0 +1,37 @@ +on: + push: + branches: + - 'forgejo' + tags: + - '*-git-annex*' + +jobs: + build-oci-image: + runs-on: docker + strategy: + matrix: + type: ["rootful", "rootless"] + steps: + - name: Determine registry and username + id: determine-registry-and-username + run: | + echo "registry=${GITHUB_SERVER_URL#https://}" >> "$GITHUB_OUTPUT" + echo "username=${GITHUB_REPOSITORY%/*}" >> "$GITHUB_OUTPUT" + - name: Install Docker + run: curl -fsSL https://get.docker.com | sh + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ steps.determine-registry-and-username.outputs.registry }} + username: ${{ steps.determine-registry-and-username.outputs.username }} + password: ${{ secrets.REGISTRY_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + file: ${{ (matrix.type == 'rootful' && 'Dockerfile') || (matrix.type == 'rootless' && 'Dockerfile.rootless') }} + push: true + tags: ${{ steps.determine-registry-and-username.outputs.registry }}/${{ github.repository }}:${{ github.ref_name }}${{ (matrix.type == 'rootful' && ' ') || (matrix.type == 'rootless' && '-rootless') }} From c659e4befad53393eee2a2d545e91b81278656fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Sat, 2 Nov 2024 15:00:59 +0000 Subject: [PATCH 046/115] Fix Forgejo version in published OCI images (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Forgejo version is derived from the git history, so the image build needs to happen in the context of a full repository clone. Also, the post-processing of the version string needs to remove the second occurrence of "-g", as the first one is now part of the added "-git-annex" part. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/51 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- .forgejo/workflows/build-oci-image.yml | 4 ++++ Makefile | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/build-oci-image.yml b/.forgejo/workflows/build-oci-image.yml index 9eabdf3eab..8e843b41ee 100644 --- a/.forgejo/workflows/build-oci-image.yml +++ b/.forgejo/workflows/build-oci-image.yml @@ -12,6 +12,9 @@ jobs: matrix: type: ["rootful", "rootless"] steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # fetch the full history so that the Forgejo version is determined properly - name: Determine registry and username id: determine-registry-and-username run: | @@ -32,6 +35,7 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: + context: . file: ${{ (matrix.type == 'rootful' && 'Dockerfile') || (matrix.type == 'rootless' && 'Dockerfile.rootless') }} push: true tags: ${{ steps.determine-registry-and-username.outputs.registry }}/${{ github.repository }}:${{ github.ref_name }}${{ (matrix.type == 'rootful' && ' ') || (matrix.type == 'rootless' && '-rootless') }} diff --git a/Makefile b/Makefile index 66f8524c04..b1272b640f 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ else FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY} else # drop the "g" prefix prepended by git describe to the commit hash - FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY} + FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/2')+${GITEA_COMPATIBILITY} endif endif FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//') From af3febc6932fefb64e444d80b538516f28cf29c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Wed, 6 Nov 2024 14:29:39 +0000 Subject: [PATCH 047/115] Explicitly set http(s) default ports in annex.url (#55) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise, git-annex tries to use its own default port (9417) and fails. Fixes #52. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/55 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- routers/web/repo/githttp.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 3e80e120b5..58a14fe26c 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -10,6 +10,7 @@ import ( gocontext "context" "fmt" "net/http" + "net/url" "os" "path/filepath" "regexp" @@ -557,7 +558,23 @@ func GetConfig(ctx *context.Context) { return } if !setting.Annex.DisableP2PHTTP { - config = append(config, []byte("[annex]\n\turl = annex+"+setting.AppURL+"git-annex-p2phttp\n")...) + appURL, err := url.Parse(setting.AppURL) + if err != nil { + log.Error("Could not parse 'setting.AppURL': %v", err) + ctx.Resp.WriteHeader(http.StatusInternalServerError) + return + } + if appURL.Port() == "" { + // If there is no port set then set the http(s) default ports. + // Without this, git-annex would try its own default port (9417) and fail. + if appURL.Scheme == "http" { + appURL.Host += ":80" + } + if appURL.Scheme == "https" { + appURL.Host += ":443" + } + } + config = append(config, []byte("[annex]\n\turl = annex+"+appURL.String()+"git-annex-p2phttp\n")...) } ctx.Resp.Header().Set("Content-Type", "text/plain") ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(config))) From 1dd008f8e19e6821d5bc993b28ae3823759f07cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Wed, 6 Nov 2024 16:22:29 +0000 Subject: [PATCH 048/115] Cache git-annex UUID to repository path mappings (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always walking the filesystem and searching for UUIDs slowed p2phttp operations down significantly on a production server with more than a handful of repositories. This caching strategy ensures that only the first call is rather slow, and subsequent ones should be much faster. This should better be implemented as a background job, but for now this is a simple solution to the problem. Fixes #53. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/54 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 5d239f7752..f9e50094f9 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -95,25 +95,43 @@ func IsAnnexRepo(repo *git.Repository) bool { var repoConfigFileRe = regexp.MustCompile("[^/]+/[^/]+.git/config$") -func UUID2RepoPath(uuid string) (string, error) { - var repoPath string - err := filepath.WalkDir(setting.RepoRootPath, func(path string, d fs.DirEntry, err error) error { +var ( + uuid2repoPathCache = make(map[string]string) + repoPath2uuidCache = make(map[string]string) +) + +func updateUUID2RepoPathCache() error { + return filepath.WalkDir(setting.RepoRootPath, func(path string, d fs.DirEntry, err error) error { if err == nil && repoConfigFileRe.MatchString(path) { thisRepoPath := strings.TrimSuffix(path, "/config") + _, ok := repoPath2uuidCache[thisRepoPath] + if ok { + return nil + } stdout, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: thisRepoPath}) if err != nil { return nil } repoUUID := strings.TrimSpace(stdout) - if repoUUID == uuid { - repoPath = thisRepoPath - return fs.SkipAll + if repoUUID != "" { + uuid2repoPathCache[repoUUID] = thisRepoPath + repoPath2uuidCache[thisRepoPath] = repoUUID } } return nil }) - if err != nil { +} + +func UUID2RepoPath(uuid string) (string, error) { + if repoPath, ok := uuid2repoPathCache[uuid]; ok { + return repoPath, nil + } + // If the cache didn't contain an entry for the UUID then update the cache and try again + if err := updateUUID2RepoPathCache(); err != nil { return "", err } - return repoPath, nil + if repoPath, ok := uuid2repoPathCache[uuid]; ok { + return repoPath, nil + } + return "", fmt.Errorf("no repository known for UUID '%s'", uuid) } From ebac40612799ec97ac445fe5e588add46207f8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Thu, 30 Jan 2025 10:50:48 +0000 Subject: [PATCH 049/115] Pre-populate the git-annex UUID cache at startup (#59) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This eliminates the wait time for the first p2phttp connection since server startup at the cost of adding that time to the startup itself. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/59 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 9 +++++++++ routers/init.go | 3 +++ 2 files changed, 12 insertions(+) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index f9e50094f9..4dabd1d415 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -20,6 +20,7 @@ import ( "strings" "forgejo.org/modules/git" + "forgejo.org/modules/log" "forgejo.org/modules/setting" ) @@ -100,6 +101,14 @@ var ( repoPath2uuidCache = make(map[string]string) ) +func Init() error { + if !setting.Annex.Enabled { + return nil + } + log.Info("Populating the git-annex UUID cache with existing repositories") + return updateUUID2RepoPathCache() +} + func updateUUID2RepoPathCache() error { return filepath.WalkDir(setting.RepoRootPath, func(path string, d fs.DirEntry, err error) error { if err == nil && repoConfigFileRe.MatchString(path) { diff --git a/routers/init.go b/routers/init.go index 90a1cb1e89..5f9dbbb4dd 100644 --- a/routers/init.go +++ b/routers/init.go @@ -11,6 +11,7 @@ import ( "forgejo.org/models" asymkey_model "forgejo.org/models/asymkey" authmodel "forgejo.org/models/auth" + "forgejo.org/modules/annex" "forgejo.org/modules/cache" "forgejo.org/modules/eventsource" "forgejo.org/modules/git" @@ -167,6 +168,8 @@ func InitWebInstalled(ctx context.Context) { actions_service.Init() + mustInit(annex.Init) + // Finally start up the cron cron.NewContext(ctx) } From 5bcf4c1821ab9e8abd4fcdd5601f34a2b96d21ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Thu, 30 Jan 2025 16:44:59 +0000 Subject: [PATCH 050/115] Invalidate outdated annex UUID cache entries (#60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous implementation could lead to errors e.g. when a repository was removed and a new one with a new UUID was created under the same name. This now checks the validity of the retrieved cache entry every time and invalidates the cache if necessary. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/60 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 4dabd1d415..09c16809f7 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -131,7 +131,7 @@ func updateUUID2RepoPathCache() error { }) } -func UUID2RepoPath(uuid string) (string, error) { +func repoPathFromUUIDCache(uuid string) (string, error) { if repoPath, ok := uuid2repoPathCache[uuid]; ok { return repoPath, nil } @@ -144,3 +144,37 @@ func UUID2RepoPath(uuid string) (string, error) { } return "", fmt.Errorf("no repository known for UUID '%s'", uuid) } + +func checkValidity(uuid, repoPath string) (bool, error) { + stdout, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repoPath}) + if err != nil { + return false, err + } + repoUUID := strings.TrimSpace(stdout) + return uuid == repoUUID, nil +} + +func removeCachedEntries(uuid, repoPath string) { + delete(uuid2repoPathCache, uuid) + delete(repoPath2uuidCache, repoPath) +} + +func UUID2RepoPath(uuid string) (string, error) { + // Get the current cache entry for the UUID + repoPath, err := repoPathFromUUIDCache(uuid) + if err != nil { + return "", err + } + // Check if it is still up-to-date + valid, err := checkValidity(uuid, repoPath) + if err != nil { + return "", err + } + if !valid { + // If it isn't, remove the cache entry and try again + removeCachedEntries(uuid, repoPath) + return UUID2RepoPath(uuid) + } + // Otherwise just return the cached entry + return repoPath, nil +} From a0ca7bfb6b3136ced6ee78d35ea4e47367ff7deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 31 Jan 2025 00:43:20 +0000 Subject: [PATCH 051/115] Use annexed content for comparison in diffs (#57) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it such that annexed files are treated like plain git files in comparisons (e.g. the diff of a commit). It also changes the image diff viewer to show a more reasonable error message when one of the annexed files under comparison is missing. Fixes #56. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/57 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 12 ++++++++++++ routers/web/repo/compare.go | 27 +++++++++++++++++++++------ web_src/js/features/imagediff.js | 12 +++++++++++- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 09c16809f7..7c3e47acb6 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -22,6 +22,7 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/log" "forgejo.org/modules/setting" + "forgejo.org/modules/typesniffer" ) // ErrBlobIsNotAnnexed occurs if a blob does not contain a valid annex key @@ -178,3 +179,14 @@ func UUID2RepoPath(uuid string) (string, error) { // Otherwise just return the cached entry return repoPath, nil } + +// GuessContentType guesses the content type of the annexed blob. +func GuessContentType(blob *git.Blob) (typesniffer.SniffedType, error) { + r, err := Content(blob) + if err != nil { + return typesniffer.SniffedType{}, err + } + defer r.Close() + + return typesniffer.DetectContentTypeFromReader(r) +} diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index db65e889e0..55d50c9a79 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -23,6 +23,7 @@ import ( repo_model "forgejo.org/models/repo" "forgejo.org/models/unit" user_model "forgejo.org/models/user" + "forgejo.org/modules/annex" "forgejo.org/modules/base" "forgejo.org/modules/charset" csv_module "forgejo.org/modules/csv" @@ -72,7 +73,21 @@ func setCompareContext(ctx *context.Context, before, head *git.Commit, headOwner return st } - st, err := blob.GuessContentType() + isAnnexed, err := annex.IsAnnexed(blob) + if err != nil { + log.Error("IsAnnexed failed: %v", err) + return st + } + if isAnnexed { + st, err = annex.GuessContentType(blob) + if err != nil { + log.Error("GuessContentType failed: %v", err) + return st + } + return st + } + + st, err = blob.GuessContentType() if err != nil { log.Error("GuessContentType failed: %v", err) return st @@ -90,18 +105,18 @@ func SourceCommitURL(owner, name string, commit *git.Commit) string { return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/src/commit/" + url.PathEscape(commit.ID.String()) } -// RawCommitURL creates a relative URL for the raw commit in the given repository -func RawCommitURL(owner, name string, commit *git.Commit) string { - return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/raw/commit/" + url.PathEscape(commit.ID.String()) +// MediaCommitURL creates a relative URL for the commit media (plain git, LFS, or annex content) in the given repository +func MediaCommitURL(owner, name string, commit *git.Commit) string { + return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/media/commit/" + url.PathEscape(commit.ID.String()) } // setPathsCompareContext sets context data for source and raw paths func setPathsCompareContext(ctx *context.Context, base, head *git.Commit, headOwner, headName string) { ctx.Data["SourcePath"] = SourceCommitURL(headOwner, headName, head) - ctx.Data["RawPath"] = RawCommitURL(headOwner, headName, head) + ctx.Data["RawPath"] = MediaCommitURL(headOwner, headName, head) if base != nil { ctx.Data["BeforeSourcePath"] = SourceCommitURL(headOwner, headName, base) - ctx.Data["BeforeRawPath"] = RawCommitURL(headOwner, headName, base) + ctx.Data["BeforeRawPath"] = MediaCommitURL(headOwner, headName, base) } } diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js index d1b139ffde..52c9017b07 100644 --- a/web_src/js/features/imagediff.js +++ b/web_src/js/features/imagediff.js @@ -92,7 +92,17 @@ export function initImageDiff() { return loadElem(img, info.path); })); // only the first images is associated with $boundsInfo - if (!success) info.$boundsInfo.text('(image error)'); + if (!success) { + const blobContent = await GET(info.path.replace('/media/', '/raw/')).then((response) => response.text()); + if (blobContent.startsWith('.git/annex/objects')) { + for (const item of document.querySelectorAll('.image-diff .overflow-menu-items .item')) { + item.style.display = 'none'; + } + info.$boundsInfo[0].parentElement.textContent = 'annexed file is not present on the server'; + } else { + info.$boundsInfo.text('(image error)'); + } + } if (info.mime === 'image/svg+xml') { const resp = await GET(info.path); const text = await resp.text(); From aba710a96293cbfde8c908584c8162408733dfbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 21 Feb 2025 13:59:06 +0000 Subject: [PATCH 052/115] fix: improve git-annex UUID cache update times (#65) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Globbing for the config files is marginally faster than walking the directory and checking for config files. Replacing the expensive calls to `git config` for each repository with reading the repository's config as an ini file is two orders of magnitude faster. All in all this reduces the required time initializing the cache for approx. 3000 repositories from approx. 5s to 50ms. The server startup now also logs how long the cache update took and the cache update is only done if p2phttp support is not disabled, because p2phttp support is currently the only feature that requires the UUID cache. Fixes #63, fixes #64. The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. - [x] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/65 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 56 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 7c3e47acb6..6fc2942e82 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -12,17 +12,18 @@ package annex import ( "errors" "fmt" - "io/fs" "os" "path" "path/filepath" - "regexp" "strings" + "time" "forgejo.org/modules/git" "forgejo.org/modules/log" "forgejo.org/modules/setting" "forgejo.org/modules/typesniffer" + + "gopkg.in/ini.v1" //nolint:depguard // This import is forbidden in favor of using the setting module, but we need ini parsing for something other than Forgejo settings ) // ErrBlobIsNotAnnexed occurs if a blob does not contain a valid annex key @@ -95,8 +96,6 @@ func IsAnnexRepo(repo *git.Repository) bool { return err == nil } -var repoConfigFileRe = regexp.MustCompile("[^/]+/[^/]+.git/config$") - var ( uuid2repoPathCache = make(map[string]string) repoPath2uuidCache = make(map[string]string) @@ -106,30 +105,39 @@ func Init() error { if !setting.Annex.Enabled { return nil } - log.Info("Populating the git-annex UUID cache with existing repositories") - return updateUUID2RepoPathCache() + if !setting.Annex.DisableP2PHTTP { + log.Info("Populating the git-annex UUID cache with existing repositories") + start := time.Now() + if err := updateUUID2RepoPathCache(); err != nil { + return err + } + log.Info("Populating the git-annex UUID cache took %v", time.Since(start)) + } + return nil } func updateUUID2RepoPathCache() error { - return filepath.WalkDir(setting.RepoRootPath, func(path string, d fs.DirEntry, err error) error { - if err == nil && repoConfigFileRe.MatchString(path) { - thisRepoPath := strings.TrimSuffix(path, "/config") - _, ok := repoPath2uuidCache[thisRepoPath] - if ok { - return nil - } - stdout, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: thisRepoPath}) - if err != nil { - return nil - } - repoUUID := strings.TrimSpace(stdout) - if repoUUID != "" { - uuid2repoPathCache[repoUUID] = thisRepoPath - repoPath2uuidCache[thisRepoPath] = repoUUID - } + configFiles, err := filepath.Glob(filepath.Join(setting.RepoRootPath, "*", "*", "config")) + if err != nil { + return err + } + for _, configFile := range configFiles { + repoPath := strings.TrimSuffix(configFile, "/config") + _, ok := repoPath2uuidCache[repoPath] + if ok { + continue } - return nil - }) + config, err := ini.Load(configFile) + if err != nil { + continue + } + repoUUID := config.Section("annex").Key("uuid").Value() + if repoUUID != "" { + uuid2repoPathCache[repoUUID] = repoPath + repoPath2uuidCache[repoPath] = repoUUID + } + } + return nil } func repoPathFromUUIDCache(uuid string) (string, error) { From a4dd1bcc4bbd37c41d10bca432b2248e8c4120a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Fri, 21 Feb 2025 21:31:48 +0000 Subject: [PATCH 053/115] feat: copy annexed files on pull request merge (#62) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the PR merge process such that annexed files contained in the to-be-merged commits are copied from the head repository to the base repository as part of the merge, similar to how it is done for LFS files. Fixes #11. The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [ ] I did not document these changes and I do not expect someone else to do it. - [x] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/62 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- modules/annex/annex.go | 56 +++++++++++++ modules/git/pipeline/catfile.go | 33 ++++++++ services/pull/annex.go | 61 ++++++++++++++ services/pull/merge.go | 7 ++ tests/integration/git_annex_test.go | 126 ++++++++++++++++++++-------- 5 files changed, 249 insertions(+), 34 deletions(-) create mode 100644 services/pull/annex.go diff --git a/modules/annex/annex.go b/modules/annex/annex.go index 6fc2942e82..26775581ca 100644 --- a/modules/annex/annex.go +++ b/modules/annex/annex.go @@ -10,12 +10,16 @@ package annex import ( + "bytes" + "context" "errors" "fmt" + "io" "os" "path" "path/filepath" "strings" + "sync" "time" "forgejo.org/modules/git" @@ -29,6 +33,16 @@ import ( // ErrBlobIsNotAnnexed occurs if a blob does not contain a valid annex key var ErrBlobIsNotAnnexed = errors.New("not a git-annex pointer") +func PrivateInit(ctx context.Context, repoPath string) error { + if _, _, err := git.NewCommand(ctx, "config", "annex.private", "true").RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + return err + } + if _, _, err := git.NewCommand(ctx, "annex", "init").RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + return err + } + return nil +} + func LookupKey(blob *git.Blob) (string, error) { stdout, _, err := git.NewCommand(git.DefaultContext, "annex", "lookupkey", "--ref").AddDynamicArguments(blob.ID.String()).RunStdString(&git.RunOpts{Dir: blob.Repo().Path}) if err != nil { @@ -38,6 +52,42 @@ func LookupKey(blob *git.Blob) (string, error) { return key, nil } +// LookupKeyBatch runs git annex lookupkey --batch --ref +func LookupKeyBatch(ctx context.Context, shasToBatchReader *io.PipeReader, lookupKeyBatchWriter *io.PipeWriter, wg *sync.WaitGroup, repoPath string) { + defer wg.Done() + defer shasToBatchReader.Close() + defer lookupKeyBatchWriter.Close() + + stderr := new(bytes.Buffer) + var errbuf strings.Builder + if err := git.NewCommand(ctx, "annex", "lookupkey", "--batch", "--ref").Run(&git.RunOpts{ + Dir: repoPath, + Stdout: lookupKeyBatchWriter, + Stdin: shasToBatchReader, + Stderr: stderr, + }); err != nil { + _ = lookupKeyBatchWriter.CloseWithError(fmt.Errorf("git annex lookupkey --batch --ref [%s]: %w - %s", repoPath, err, errbuf.String())) + } +} + +// CopyFromToBatch runs git -c annex.hardlink=true annex copy --batch-keys --from --to +func CopyFromToBatch(ctx context.Context, from, to string, keysToCopyReader *io.PipeReader, wg *sync.WaitGroup, repoPath string) { + defer wg.Done() + defer keysToCopyReader.Close() + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + var errbuf strings.Builder + if err := git.NewCommand(ctx, "-c", "annex.hardlink=true", "annex", "copy", "--batch-keys", "--from").AddDynamicArguments(from).AddArguments("--to").AddDynamicArguments(to).Run(&git.RunOpts{ + Dir: repoPath, + Stdout: stdout, + Stdin: keysToCopyReader, + Stderr: stderr, + }); err != nil { + _ = keysToCopyReader.CloseWithError(fmt.Errorf("git annex copy --batch-keys --from --to [%s]: %w - %s", repoPath, err, errbuf.String())) + } +} + func ContentLocationFromKey(repoPath, key string) (string, error) { contentLocation, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, "annex", "contentlocation").AddDynamicArguments(key).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { @@ -90,6 +140,12 @@ func IsAnnexed(blob *git.Blob) (bool, error) { return true, nil } +// PathIsAnnexRepo determines if repoPath is a git-annex enabled repository +func PathIsAnnexRepo(repoPath string) bool { + _, _, err := git.NewCommand(git.DefaultContext, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repoPath}) + return err == nil +} + // IsAnnexRepo determines if repo is a git-annex enabled repository func IsAnnexRepo(repo *git.Repository) bool { _, _, err := git.NewCommand(repo.Ctx, "config", "annex.uuid").RunStdString(&git.RunOpts{Dir: repo.Path}) diff --git a/modules/git/pipeline/catfile.go b/modules/git/pipeline/catfile.go index 476f876e2b..6ada51ae82 100644 --- a/modules/git/pipeline/catfile.go +++ b/modules/git/pipeline/catfile.go @@ -106,3 +106,36 @@ func BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader *io.PipeReader, s } } } + +// BlobsLessThanOrEqual32KiBFromCatFileBatchCheck reads a pipeline from cat-file --batch-check and returns the blobs <=32KiB in size +func BlobsLessThanOrEqual32KiBFromCatFileBatchCheck(catFileCheckReader *io.PipeReader, shasToBatchWriter *io.PipeWriter, wg *sync.WaitGroup) { + defer wg.Done() + defer catFileCheckReader.Close() + scanner := bufio.NewScanner(catFileCheckReader) + defer func() { + _ = shasToBatchWriter.CloseWithError(scanner.Err()) + }() + for scanner.Scan() { + line := scanner.Text() + if len(line) == 0 { + continue + } + fields := strings.Split(line, " ") + if len(fields) < 3 || fields[1] != "blob" { + continue + } + size, _ := strconv.Atoi(fields[2]) + if size > 32*1024 { + continue + } + toWrite := []byte(fields[0] + "\n") + for len(toWrite) > 0 { + n, err := shasToBatchWriter.Write(toWrite) + if err != nil { + _ = catFileCheckReader.CloseWithError(err) + break + } + toWrite = toWrite[n:] + } + } +} diff --git a/services/pull/annex.go b/services/pull/annex.go new file mode 100644 index 0000000000..67985aa1b7 --- /dev/null +++ b/services/pull/annex.go @@ -0,0 +1,61 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pull + +import ( + "context" + "io" + "sync" + + "forgejo.org/modules/annex" + "forgejo.org/modules/git/pipeline" +) + +// AnnexPush copies all annexed files referenced in new commits from the head repository to the base repository +func AnnexPush(ctx context.Context, tmpBasePath, mergeHeadSHA, mergeBaseSHA string) error { + // Initialize the temporary repository with git-annex + if err := annex.PrivateInit(ctx, tmpBasePath); err != nil { + return err + } + + revListReader, revListWriter := io.Pipe() + shasToCheckReader, shasToCheckWriter := io.Pipe() + catFileCheckReader, catFileCheckWriter := io.Pipe() + shasToBatchReader, shasToBatchWriter := io.Pipe() + lookupKeyBatchReader, lookupKeyBatchWriter := io.Pipe() + errChan := make(chan error, 1) + wg := sync.WaitGroup{} + wg.Add(6) + // Create the go-routines in reverse order. + + // 6. Take the referenced keys and copy their data from the head repository to + // the base repository + go annex.CopyFromToBatch(ctx, "head_repo", "origin", lookupKeyBatchReader, &wg, tmpBasePath) + + // 5. Take the shas of the blobs and resolve them to annex keys, git-annex + // should filter out anything that doesn't reference a key + go annex.LookupKeyBatch(ctx, shasToBatchReader, lookupKeyBatchWriter, &wg, tmpBasePath) + + // 4. From the provided objects restrict to blobs <=32KiB + go pipeline.BlobsLessThanOrEqual32KiBFromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg) + + // 3. Run batch-check on the objects retrieved from rev-list + go pipeline.CatFileBatchCheck(ctx, shasToCheckReader, catFileCheckWriter, &wg, tmpBasePath) + + // 2. Check each object retrieved rejecting those without names as they will be commits or trees + go pipeline.BlobsFromRevListObjects(revListReader, shasToCheckWriter, &wg) + + // 1. Run rev-list objects from mergeHead to mergeBase + go pipeline.RevListObjects(ctx, revListWriter, &wg, tmpBasePath, mergeHeadSHA, mergeBaseSHA, errChan) + + wg.Wait() + select { + case err, has := <-errChan: + if has { + return err + } + default: + } + return nil +} diff --git a/services/pull/merge.go b/services/pull/merge.go index 9b0d632377..2f6ee6754c 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -22,6 +22,7 @@ import ( repo_model "forgejo.org/models/repo" "forgejo.org/models/unit" user_model "forgejo.org/models/user" + "forgejo.org/modules/annex" "forgejo.org/modules/cache" "forgejo.org/modules/git" "forgejo.org/modules/log" @@ -314,6 +315,12 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use } } + if setting.Annex.Enabled && annex.PathIsAnnexRepo(pr.BaseRepo.RepoPath()) && annex.PathIsAnnexRepo(pr.HeadRepo.RepoPath()) { + if err := AnnexPush(ctx, mergeCtx.tmpBasePath, mergeHeadSHA, mergeBaseSHA); err != nil { + return "", err + } + } + var headUser *user_model.User err = pr.HeadRepo.LoadOwner(ctx) if err != nil { diff --git a/tests/integration/git_annex_test.go b/tests/integration/git_annex_test.go index 14792af066..1b832db1fb 100644 --- a/tests/integration/git_annex_test.go +++ b/tests/integration/git_annex_test.go @@ -28,9 +28,11 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/setting" api "forgejo.org/modules/structs" + "forgejo.org/modules/test" "forgejo.org/modules/util" "forgejo.org/tests" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -62,6 +64,95 @@ func doCreateRemoteAnnexRepository(t *testing.T, u *url.URL, ctx APITestContext, return nil } +func TestGitAnnexPullRequest(t *testing.T) { + if !setting.Annex.Enabled { + t.Skip("Skipping since annex support is disabled.") + } + defer tests.PrepareTestEnv(t)() + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { + upstreamRepoName := "annex-pull-request-test-" + objectFormat.Name() + forkRepoName := upstreamRepoName + ctx := NewAPITestContext(t, "user2", upstreamRepoName, auth_model.AccessTokenScopeWriteRepository) + require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false, objectFormat)) + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", upstreamRepoName, "user1", forkRepoName) + + // Generate random file + tmpFile := path.Join(t.TempDir(), "somefile") + require.NoError(t, generateRandomFile(1024*1024/4, tmpFile)) + expectedContent, err := os.ReadFile(tmpFile) + require.NoError(t, err) + + testUploadFile(t, session, "user1", forkRepoName, setting.Repository.DefaultBranch, filepath.Base(tmpFile), tmpFile) + + resp := testPullCreate(t, session, "user1", forkRepoName, false, setting.Repository.DefaultBranch, setting.Repository.DefaultBranch, "Testing git-annex content in a pull request") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) + + // Get some handles on the target repository and file + remoteRepoPath := path.Join(setting.RepoRootPath, ctx.GitPath()) + repo, err := git.OpenRepository(git.DefaultContext, remoteRepoPath) + require.NoError(t, err) + defer repo.Close() + tree, err := repo.GetTree(setting.Repository.DefaultBranch) + require.NoError(t, err) + treeEntry, err := tree.GetTreeEntryByPath(filepath.Base(tmpFile)) + require.NoError(t, err) + blob := treeEntry.Blob() + + // Check that the pull request file is annexed + isAnnexed, err := annex.IsAnnexed(blob) + require.NoError(t, err) + require.True(t, isAnnexed) + + // Check that the pull request file has the correct content + annexedFile, err := annex.Content(blob) + require.NoError(t, err) + actualContent, err := io.ReadAll(annexedFile) + require.NoError(t, err) + require.Equal(t, expectedContent, actualContent) + }) + }) +} + +func testUploadFile(t *testing.T, session *TestSession, username, reponame, branch, filename, path string) { + t.Helper() + + body := &bytes.Buffer{} + mpForm := multipart.NewWriter(body) + err := mpForm.WriteField("_csrf", GetCSRF(t, session, username+"/"+reponame+"/_upload/"+branch)) + require.NoError(t, err) + + file, err := mpForm.CreateFormFile("file", filename) + require.NoError(t, err) + + srcFile, err := os.Open(path) + require.NoError(t, err) + + io.Copy(file, srcFile) + require.NoError(t, mpForm.Close()) + + req := NewRequestWithBody(t, "POST", "/"+username+"/"+reponame+"/upload-file", body) + req.Header.Add("Content-Type", mpForm.FormDataContentType()) + resp := session.MakeRequest(t, req, http.StatusOK) + + respMap := map[string]string{} + DecodeJSON(t, resp, &respMap) + fileUUID := respMap["uuid"] + + req = NewRequestWithValues(t, "POST", username+"/"+reponame+"/_upload/"+branch, map[string]string{ + "commit_choice": "direct", + "files": fileUUID, + "_csrf": GetCSRF(t, session, username+"/"+reponame+"/_upload/"+branch), + "commit_mail_id": "-1", + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + func TestGitAnnexWebUpload(t *testing.T) { if !setting.Annex.Enabled { t.Skip("Skipping since annex support is disabled.") @@ -72,32 +163,6 @@ func TestGitAnnexWebUpload(t *testing.T) { ctx := NewAPITestContext(t, "user2", "annex-web-upload-test"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository) require.NoError(t, doCreateRemoteAnnexRepository(t, u, ctx, false, objectFormat)) - uploadFile := func(t *testing.T, path string) string { - t.Helper() - - body := &bytes.Buffer{} - mpForm := multipart.NewWriter(body) - err := mpForm.WriteField("_csrf", GetCSRF(t, ctx.Session, ctx.Username+"/"+ctx.Reponame+"/_upload/"+setting.Repository.DefaultBranch)) - require.NoError(t, err) - - file, err := mpForm.CreateFormFile("file", filepath.Base(path)) - require.NoError(t, err) - - srcFile, err := os.Open(path) - require.NoError(t, err) - - io.Copy(file, srcFile) - require.NoError(t, mpForm.Close()) - - req := NewRequestWithBody(t, "POST", "/"+ctx.Username+"/"+ctx.Reponame+"/upload-file", body) - req.Header.Add("Content-Type", mpForm.FormDataContentType()) - resp := ctx.Session.MakeRequest(t, req, http.StatusOK) - - respMap := map[string]string{} - DecodeJSON(t, resp, &respMap) - return respMap["uuid"] - } - // Generate random file tmpFile := path.Join(t.TempDir(), "web-upload-test-file.bin") require.NoError(t, generateRandomFile(1024*1024/4, tmpFile)) @@ -105,14 +170,7 @@ func TestGitAnnexWebUpload(t *testing.T) { require.NoError(t, err) // Upload generated file - fileUUID := uploadFile(t, tmpFile) - req := NewRequestWithValues(t, "POST", ctx.Username+"/"+ctx.Reponame+"/_upload/"+setting.Repository.DefaultBranch, map[string]string{ - "commit_choice": "direct", - "files": fileUUID, - "_csrf": GetCSRF(t, ctx.Session, ctx.Username+"/"+ctx.Reponame+"/_upload/"+setting.Repository.DefaultBranch), - "commit_mail_id": "-1", - }) - ctx.Session.MakeRequest(t, req, http.StatusSeeOther) + testUploadFile(t, ctx.Session, ctx.Username, ctx.Reponame, setting.Repository.DefaultBranch, filepath.Base(tmpFile), tmpFile) // Get some handles on the target repository and file remoteRepoPath := path.Join(setting.RepoRootPath, ctx.GitPath()) From 8e1967484000f81b32f6de9c6e0f3a413b1056bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Ri=C3=9Fe?= Date: Mon, 17 Mar 2025 09:58:47 +0000 Subject: [PATCH 054/115] fix: set git identity for p2phttp processes (#70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In some situations it could happen that `git annex p2phttp` needs some kind of maintenance work resulting in a commit, but without a configured git identity p2phttp would refuse to run. This could break p2phttp support. Setting `GIT_AUTHOR_{NAME,EMAIL}` should remedy this issue. Fixes #69. The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. - [x] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/70 Co-authored-by: Matthias Riße Co-committed-by: Matthias Riße --- routers/web/repo/annex.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routers/web/repo/annex.go b/routers/web/repo/annex.go index facdded335..191ad5dd20 100644 --- a/routers/web/repo/annex.go +++ b/routers/web/repo/annex.go @@ -19,6 +19,7 @@ import ( "forgejo.org/modules/annex" "forgejo.org/modules/graceful" "forgejo.org/modules/log" + "forgejo.org/modules/setting" services_context "forgejo.org/services/context" ) @@ -93,6 +94,10 @@ func AnnexP2PHTTP(ctx *services_context.Context) { Pdeathsig: syscall.SIGINT, } cmd.Cancel = func() error { return cmd.Process.Signal(os.Interrupt) } + cmd.Env = append(os.Environ(), + "GIT_AUTHOR_NAME="+setting.AppName, + "GIT_AUTHOR_EMAIL="+setting.RunUser+"@"+setting.Domain, + ) _ = cmd.Run() }(p2phttpCtx) graceful.GetManager().RunAtTerminate(p2phttpCtxCancel) From 19be11668b34d994c6dd59d7ebda47ec7995374c Mon Sep 17 00:00:00 2001 From: matrss Date: Thu, 10 Apr 2025 12:52:29 +0000 Subject: [PATCH 055/115] Forgejo-aneksajo branding (#71) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original logo was created by Caesar Schinas and is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license. This implies permission to remix. Source: https://codeberg.org/forgejo/governance/src/branch/main/branding#logo The git-annex logo is covered by ``` Copyright: 2007 Henrik Nyh 2010 Joey Hess 2013 John Lawrence 2024 Yann Büchau License: other Free to modify and redistribute with due credit, and obviously free to use. ``` Source: https://git-annex.branchable.com/logo/ Co-authored-by: Michael Hanke Reviewed-on: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/71 --- assets/favicon.svg | 56 +++++++++++++++++++++++++--------------------- assets/logo.svg | 56 +++++++++++++++++++++++++--------------------- 2 files changed, 62 insertions(+), 50 deletions(-) diff --git a/assets/favicon.svg b/assets/favicon.svg index bcacdc0200..bb0031b93d 100644 --- a/assets/favicon.svg +++ b/assets/favicon.svg @@ -1,27 +1,33 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/assets/logo.svg b/assets/logo.svg index bcacdc0200..bb0031b93d 100644 --- a/assets/logo.svg +++ b/assets/logo.svg @@ -1,27 +1,33 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + From b067d0df6e6441fd47ce51ff44aa57301decf4ab Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Fri, 25 Apr 2025 12:38:14 +0000 Subject: [PATCH 056/115] [v11.0/forgejo] fix: display the list of tasks in the runner edit page (#7652) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7650 A regression was introduced in Forgejo v11.0 that caused the edit page of a runner (e.g. https://example.org/admin/actions/runners/434) to no longer display the tasks associated with the runner. Fixes https://codeberg.org/forgejo/forgejo/issues/7643 --- When the fix is absent, the tests fail like so: ``` --- FAIL: TestRunnerDetails (0.03s) --- FAIL: TestRunnerDetails/first_page (0.00s) runners_test.go:36: Error Trace: /home/earl-warren/software/forgejo/routers/web/shared/actions/runners_test.go:36 Error: "[]" should have 30 item(s), but has 0 Test: TestRunnerDetails/first_page --- FAIL: TestRunnerDetails/second_and_last_page (0.00s) runners_test.go:43: Error Trace: /home/earl-warren/software/forgejo/routers/web/shared/actions/runners_test.go:43 Error: "[]" should have 10 item(s), but has 0 Test: TestRunnerDetails/second_and_last_page FAIL FAIL forgejo.org/routers/web/shared/actions 0.170s ``` ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/.md` to be be used for the release notes instead of the title. ## Release notes - User Interface bug fixes - [PR](https://codeberg.org/forgejo/forgejo/pulls/7650): display the list of tasks in the runner edit page ## Release notes - User Interface bug fixes - [PR](https://codeberg.org/forgejo/forgejo/pulls/7650): display the list of tasks in the runner edit page Co-authored-by: Earl Warren Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7652 Reviewed-by: Earl Warren Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- .../TestRunnerDetails/action_runner.yml | 7 + .../TestRunnerDetails/action_task.yml | 160 ++++++++++++++++++ routers/web/shared/actions/main_test.go | 17 ++ routers/web/shared/actions/runners.go | 1 - routers/web/shared/actions/runners_test.go | 47 +++++ 5 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 routers/web/shared/actions/fixtures/TestRunnerDetails/action_runner.yml create mode 100644 routers/web/shared/actions/fixtures/TestRunnerDetails/action_task.yml create mode 100644 routers/web/shared/actions/main_test.go create mode 100644 routers/web/shared/actions/runners_test.go diff --git a/routers/web/shared/actions/fixtures/TestRunnerDetails/action_runner.yml b/routers/web/shared/actions/fixtures/TestRunnerDetails/action_runner.yml new file mode 100644 index 0000000000..d783f83110 --- /dev/null +++ b/routers/web/shared/actions/fixtures/TestRunnerDetails/action_runner.yml @@ -0,0 +1,7 @@ +- + id: 1004 + uuid: "fb857e63-c0ce-4571-a6c9-fde26c128073" + name: "Global runner" + owner_id: 0 + repo_id: 0 + deleted: 0 diff --git a/routers/web/shared/actions/fixtures/TestRunnerDetails/action_task.yml b/routers/web/shared/actions/fixtures/TestRunnerDetails/action_task.yml new file mode 100644 index 0000000000..63a2d30deb --- /dev/null +++ b/routers/web/shared/actions/fixtures/TestRunnerDetails/action_task.yml @@ -0,0 +1,160 @@ +- + id: 1 + runner_id: 1004 + token_hash: a1 +- + id: 2 + runner_id: 1004 + token_hash: a2 +- + id: 3 + runner_id: 1004 + token_hash: a3 +- + id: 4 + runner_id: 1004 + token_hash: a4 +- + id: 5 + runner_id: 1004 + token_hash: a5 +- + id: 6 + runner_id: 1004 + token_hash: a6 +- + id: 7 + runner_id: 1004 + token_hash: a7 +- + id: 8 + runner_id: 1004 + token_hash: a8 +- + id: 9 + runner_id: 1004 + token_hash: a9 +- + id: 10 + runner_id: 1004 + token_hash: a10 +- + id: 11 + runner_id: 1004 + token_hash: a11 +- + id: 12 + runner_id: 1004 + token_hash: a12 +- + id: 13 + runner_id: 1004 + token_hash: a13 +- + id: 14 + runner_id: 1004 + token_hash: a14 +- + id: 15 + runner_id: 1004 + token_hash: a15 +- + id: 16 + runner_id: 1004 + token_hash: a16 +- + id: 17 + runner_id: 1004 + token_hash: a17 +- + id: 18 + runner_id: 1004 + token_hash: a18 +- + id: 19 + runner_id: 1004 + token_hash: a19 +- + id: 20 + runner_id: 1004 + token_hash: a20 +- + id: 21 + runner_id: 1004 + token_hash: a21 +- + id: 22 + runner_id: 1004 + token_hash: a22 +- + id: 23 + runner_id: 1004 + token_hash: a23 +- + id: 24 + runner_id: 1004 + token_hash: a24 +- + id: 25 + runner_id: 1004 + token_hash: a25 +- + id: 26 + runner_id: 1004 + token_hash: a26 +- + id: 27 + runner_id: 1004 + token_hash: a27 +- + id: 28 + runner_id: 1004 + token_hash: a28 +- + id: 29 + runner_id: 1004 + token_hash: a29 +- + id: 30 + runner_id: 1004 + token_hash: a30 +- + id: 31 + runner_id: 1004 + token_hash: a31 +- + id: 32 + runner_id: 1004 + token_hash: a32 +- + id: 33 + runner_id: 1004 + token_hash: a33 +- + id: 34 + runner_id: 1004 + token_hash: a34 +- + id: 35 + runner_id: 1004 + token_hash: a35 +- + id: 36 + runner_id: 1004 + token_hash: a36 +- + id: 37 + runner_id: 1004 + token_hash: a37 +- + id: 38 + runner_id: 1004 + token_hash: a38 +- + id: 39 + runner_id: 1004 + token_hash: a39 +- + id: 40 + runner_id: 1004 + token_hash: a40 diff --git a/routers/web/shared/actions/main_test.go b/routers/web/shared/actions/main_test.go new file mode 100644 index 0000000000..056f48b98d --- /dev/null +++ b/routers/web/shared/actions/main_test.go @@ -0,0 +1,17 @@ +// Copyright 2025 The Forgejo Authors. +// SPDX-License-Identifier: GPL-3.0-or-later + +package actions + +import ( + "testing" + + "forgejo.org/models/unittest" + + _ "forgejo.org/models" + _ "forgejo.org/models/forgefed" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index 98a649d1d8..2ab6b2dadd 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -79,7 +79,6 @@ func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int Page: page, PageSize: 30, }, - Status: []actions_model.Status{actions_model.StatusUnknown}, // Unknown means all RunnerID: runner.ID, } diff --git a/routers/web/shared/actions/runners_test.go b/routers/web/shared/actions/runners_test.go new file mode 100644 index 0000000000..ad75d34ee6 --- /dev/null +++ b/routers/web/shared/actions/runners_test.go @@ -0,0 +1,47 @@ +// Copyright 2025 The Forgejo Authors. +// SPDX-License-Identifier: GPL-3.0-or-later + +package actions + +import ( + "net/http" + "testing" + + actions_model "forgejo.org/models/actions" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + "forgejo.org/services/contexttest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRunnerDetails(t *testing.T) { + defer unittest.OverrideFixtures("routers/web/shared/actions/fixtures/TestRunnerDetails")() + require.NoError(t, unittest.PrepareTestDatabase()) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: 1004}) + + t.Run("permission denied", func(t *testing.T) { + ctx, resp := contexttest.MockContext(t, "/admin/actions/runners") + RunnerDetails(ctx, 1, runner.ID, user.ID, 0) + assert.Equal(t, http.StatusNotFound, resp.Code) + }) + + t.Run("first page", func(t *testing.T) { + ctx, resp := contexttest.MockContext(t, "/admin/actions/runners") + page := 1 + RunnerDetails(ctx, page, runner.ID, 0, 0) + require.Equal(t, http.StatusOK, resp.Code) + assert.Len(t, ctx.GetData()["Tasks"], 30) + }) + + t.Run("second and last page", func(t *testing.T) { + ctx, resp := contexttest.MockContext(t, "/admin/actions/runners") + page := 2 + RunnerDetails(ctx, page, runner.ID, 0, 0) + require.Equal(t, http.StatusOK, resp.Code) + assert.Len(t, ctx.GetData()["Tasks"], 10) + }) +} From bc6c0b610b841469bc170486a4875bb80e5deb86 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Sun, 27 Apr 2025 21:25:42 +0000 Subject: [PATCH 057/115] [v11.0/forgejo] fix: set default restricted for OAuth2 user (#7688) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7683 - The OAuthCallback code that is responsible for creating a new user, if one does not exist yet, did not use `[service].ALLOW_ONLY_EXTERNAL_REGISTRATION` as default value for the restricted field of a user. - Resolves forgejo/forgejo#7681 - Add integration test. Co-authored-by: Gusted Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7688 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- routers/web/auth/oauth.go | 2 +- tests/integration/oauth_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index f1554638cb..c55397da88 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1079,7 +1079,7 @@ func SignInOAuthCallback(ctx *context.Context) { isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser) u.IsAdmin = isAdmin.ValueOrDefault(false) - u.IsRestricted = isRestricted.ValueOrDefault(false) + u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted) if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { // error already handled diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index a4dea51a07..1f7aca90ad 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -1431,3 +1431,28 @@ func TestOAuth_GrantScopesReadPublicGroupsWithTheReadScope(t *testing.T) { assert.Contains(t, parsedUserInfo.Groups, privOrg) } } + +func TestSignUpViaOAuthDefaultRestricted(t *testing.T) { + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.Service.DefaultUserIsRestricted, true)() + + gitlabName := "gitlab" + addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName)) + userGitLabUserID := "BB(5)=47176870" + + defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + return goth.User{ + Provider: gitlabName, + UserID: userGitLabUserID, + Name: "gitlab-user", + NickName: "gitlab-user", + Email: "gitlab@example.com", + }, nil + })() + req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName)) + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", test.RedirectURL(resp)) + + unittest.AssertExistsIf(t, true, &user_model.User{Name: "gitlab-user"}, "is_restricted = true") +} From e83735031939d9e5e69ff9cf196f6d9768463a13 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Mon, 28 Apr 2025 06:52:28 +0000 Subject: [PATCH 058/115] [v11.0/forgejo] fix: use `linguist-generated` for language stats (#7694) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7685 - Adds code comment to explain behavior of the `linguist-generated` gitattribute. - Adjusts the code to ignore the file if `linguist-generated` is true. - Resolves forgejo/forgejo#7677 - Adds unit testing. ## Release notes - Bug fixes - [PR](https://codeberg.org/forgejo/forgejo/pulls/7694): fix: use `linguist-generated` for language stats Co-authored-by: Gusted Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7694 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- modules/git/repo_language_stats.go | 8 +++++++- modules/git/repo_language_stats_test.go | 9 +++++++++ .../git/tests/repos/language_stats_repo/index | Bin 553 -> 0 bytes .../tests/repos/language_stats_repo/logs/HEAD | 2 -- .../language_stats_repo/logs/refs/heads/master | 2 -- .../1e/ea60592b55dcb45c36029cc1202132e9fb756c | Bin 63 -> 0 bytes .../22/b6aa0588563508d8879f062470c8cbc7b2f2bb | Bin 200 -> 0 bytes .../34/1fca5b5ea3de596dc483e54c2db28633cd2f97 | Bin 172 -> 0 bytes .../42/25ecfaf6bafbcfa31ea5cbd8121c36d9457085 | Bin 116 -> 0 bytes .../4a/c803638e4b8995146e329a05e096fa2c77a03d | Bin 73 -> 0 bytes .../64/4c37ad7fe64ac012df7e59d27a92e3137c640e | Bin 53 -> 0 bytes .../6c/633a0067b463e459ae952716b17ae36aa30adc | Bin 371 -> 0 bytes .../8e/b563dc106e3dfd3ad0fa81f7a0c5e2604f80cd | Bin 201 -> 0 bytes .../8f/ee858da5796dfb37704761701bb8e800ad9ef3 | Bin 828 -> 0 bytes .../aa/a21bf84c8b2304608d3fc83b747840f2456299 | Bin 54 -> 0 bytes .../da/a5abe3c5f42cae598e362e8a8db6284565d6bb | Bin 64 -> 0 bytes ...-371b1f6c24df14da4898b22c00ff8fb55303ac76.idx | Bin 0 -> 1520 bytes ...371b1f6c24df14da4898b22c00ff8fb55303ac76.pack | Bin 0 -> 2748 bytes ...-371b1f6c24df14da4898b22c00ff8fb55303ac76.rev | Bin 0 -> 116 bytes .../tests/repos/language_stats_repo/packed-refs | 2 ++ .../language_stats_repo/refs/heads/.gitkeep | 0 .../repos/language_stats_repo/refs/heads/master | 1 - .../repos/language_stats_repo/refs/tags/.gitkeep | 0 23 files changed, 18 insertions(+), 6 deletions(-) delete mode 100644 modules/git/tests/repos/language_stats_repo/index delete mode 100644 modules/git/tests/repos/language_stats_repo/logs/HEAD delete mode 100644 modules/git/tests/repos/language_stats_repo/logs/refs/heads/master delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/1e/ea60592b55dcb45c36029cc1202132e9fb756c delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/22/b6aa0588563508d8879f062470c8cbc7b2f2bb delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/34/1fca5b5ea3de596dc483e54c2db28633cd2f97 delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/42/25ecfaf6bafbcfa31ea5cbd8121c36d9457085 delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/4a/c803638e4b8995146e329a05e096fa2c77a03d delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/64/4c37ad7fe64ac012df7e59d27a92e3137c640e delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/6c/633a0067b463e459ae952716b17ae36aa30adc delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/8e/b563dc106e3dfd3ad0fa81f7a0c5e2604f80cd delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/8f/ee858da5796dfb37704761701bb8e800ad9ef3 delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/aa/a21bf84c8b2304608d3fc83b747840f2456299 delete mode 100644 modules/git/tests/repos/language_stats_repo/objects/da/a5abe3c5f42cae598e362e8a8db6284565d6bb create mode 100644 modules/git/tests/repos/language_stats_repo/objects/pack/pack-371b1f6c24df14da4898b22c00ff8fb55303ac76.idx create mode 100644 modules/git/tests/repos/language_stats_repo/objects/pack/pack-371b1f6c24df14da4898b22c00ff8fb55303ac76.pack create mode 100644 modules/git/tests/repos/language_stats_repo/objects/pack/pack-371b1f6c24df14da4898b22c00ff8fb55303ac76.rev create mode 100644 modules/git/tests/repos/language_stats_repo/packed-refs create mode 100644 modules/git/tests/repos/language_stats_repo/refs/heads/.gitkeep delete mode 100644 modules/git/tests/repos/language_stats_repo/refs/heads/master create mode 100644 modules/git/tests/repos/language_stats_repo/refs/tags/.gitkeep diff --git a/modules/git/repo_language_stats.go b/modules/git/repo_language_stats.go index 24879a26fe..7b76c7bcc7 100644 --- a/modules/git/repo_language_stats.go +++ b/modules/git/repo_language_stats.go @@ -204,7 +204,13 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, err } } - if !isTrue(isGenerated) && enry.IsGenerated(f.Name(), content) { + + // We consider three cases: + // 1. linguist-generated=true, then we ignore the file. + // 2. linguist-generated=false, we don't ignore the file. + // 3. linguist-generated is not set, then `enry.IsGenerated` determines if the file is generated. + if isTrue(isGenerated) || !isFalse(isGenerated) && enry.IsGenerated(f.Name(), content) { + log.Trace("Ignore %q for language stats, because it is generated", f.Name()) continue } diff --git a/modules/git/repo_language_stats_test.go b/modules/git/repo_language_stats_test.go index fd80e44a86..ccd7301f81 100644 --- a/modules/git/repo_language_stats_test.go +++ b/modules/git/repo_language_stats_test.go @@ -25,6 +25,15 @@ func TestRepository_GetLanguageStats(t *testing.T) { "Python": 134, "Java": 112, }, stats) + + stats, err = gitRepo.GetLanguageStats("95d3505f2db273e40be79f84416051ae85e9ea0d") + require.NoError(t, err) + + assert.Equal(t, map[string]int64{ + "Cobra": 67, + "Python": 67, + "Java": 112, + }, stats) } func TestMergeLanguageStats(t *testing.T) { diff --git a/modules/git/tests/repos/language_stats_repo/index b/modules/git/tests/repos/language_stats_repo/index deleted file mode 100644 index e6c02231716df89a12fa7ee496d2b8f681527d8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmZ?q402{*U|<4b)`@?5BjE0&w7tK6p zuM^D4ecqi@Me>Yhu|AmgOQ(E+Edzh1Zep%(qHaNDNk)F2UIEy=s`YZph6fToAT-E4 zMggaAH1i6aRNwsiw(Ix##d1qe-w={9yXjic${>=JSeB@pk(!f}ub-QknWqQhgIo!6 zXZ0rqH9J8V4Rz;KqRbTl8&;N@my%xuH4|u#y8OSd*)SSx-W_R9H1n|gTNL79s7t}_ z)hnn3yE7!n)fMDi22%wiuD-3wcLefm|5{!6)%bnE(MJjX4QCNvWH3}P;99jv`iD=q zGD||Q{R!)m3WrawNi&g62P;bPF<)E%% 1632140318 +0100 commit (initial): Add some test files for GetLanguageStats -8fee858da5796dfb37704761701bb8e800ad9ef3 341fca5b5ea3de596dc483e54c2db28633cd2f97 oliverpool 1711278775 +0100 push diff --git a/modules/git/tests/repos/language_stats_repo/logs/refs/heads/master b/modules/git/tests/repos/language_stats_repo/logs/refs/heads/master deleted file mode 100644 index 9cedbb66a9..0000000000 --- a/modules/git/tests/repos/language_stats_repo/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 8fee858da5796dfb37704761701bb8e800ad9ef3 Andrew Thornton 1632140318 +0100 commit (initial): Add some test files for GetLanguageStats -8fee858da5796dfb37704761701bb8e800ad9ef3 341fca5b5ea3de596dc483e54c2db28633cd2f97 oliverpool 1711278775 +0100 push diff --git a/modules/git/tests/repos/language_stats_repo/objects/1e/ea60592b55dcb45c36029cc1202132e9fb756c b/modules/git/tests/repos/language_stats_repo/objects/1e/ea60592b55dcb45c36029cc1202132e9fb756c deleted file mode 100644 index 3c55bab91e0f9b29be108b34ab989900c6e68e08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63 zcmV-F0Korv0ZYosPf{>5WYE$pOU+BkFVf3OEK5|#$;?YH%`7g_g$SmmaB1lkAVhN# VfzpZTskYAfNkxfVTmV4s7y72Y9s2+P diff --git a/modules/git/tests/repos/language_stats_repo/objects/22/b6aa0588563508d8879f062470c8cbc7b2f2bb b/modules/git/tests/repos/language_stats_repo/objects/22/b6aa0588563508d8879f062470c8cbc7b2f2bb deleted file mode 100644 index 947feecea9cd7632d10dbd328570b6e121568d07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 200 zcmV;(05|`50V^p=O;s>5Fl8__FfcPQQP4}zEJ-XWDauSLElDkAkb9L7sU3P}ON<%Q zoP!FAMlXMt=0H_u>L%vuCh8VcmSp7T=@l?|onTJx^X{A~l4mrF^})1XI^_#&O$>lQ zAuF*gQ8yzsCnuj_)gtL1KHbVJ3BC3wtV=2!KDj2%gqoL|n3<|7 diff --git a/modules/git/tests/repos/language_stats_repo/objects/34/1fca5b5ea3de596dc483e54c2db28633cd2f97 b/modules/git/tests/repos/language_stats_repo/objects/34/1fca5b5ea3de596dc483e54c2db28633cd2f97 deleted file mode 100644 index 9ce337e070c1d098809ccef501f8c27db034ffe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmV;d08{^X0iBLPZUZ3<0DI;YKDW}!uz@lSBAWa>ocfAumcK@t ab2Pb4J;3|2L;Hbi|ApLTB=Z1IMNSlufK+Gz diff --git a/modules/git/tests/repos/language_stats_repo/objects/42/25ecfaf6bafbcfa31ea5cbd8121c36d9457085 b/modules/git/tests/repos/language_stats_repo/objects/42/25ecfaf6bafbcfa31ea5cbd8121c36d9457085 deleted file mode 100644 index ff3b642734150dc12c49eb637fb6c9322012ae8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116 zcmV-)0E_>40ZYosPf{>6G-4^kN#UyIQh-Vomn4<| zRg~ptrYPhlX69)GmlS2@rANmqBo?I?YjP<7l_~^R7MG;v>gAV~=oJ8EOLFowl)$Db Wl!J^@RMNEOQmEys5XHZsF2+7DSRsf9V=y!@Ff%bx$Vkn}$=55WWbit{oZRQ#IaMUjXcp^(X}@&J L7uW&-FaZx{HjWkw diff --git a/modules/git/tests/repos/language_stats_repo/objects/6c/633a0067b463e459ae952716b17ae36aa30adc b/modules/git/tests/repos/language_stats_repo/objects/6c/633a0067b463e459ae952716b17ae36aa30adc deleted file mode 100644 index 873cb7187dfe5934941716569ab1b76fe181f23d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 371 zcmbKk9!7p(QRo{{}nsGxXNbMQs3M+H9}J7S6z%X3#Q{iUwa zRNub1`N4eUf0FmE?yztv2;Rr8uYbI{vS7Zh%ES;sX%Ht8!VzwAmpaI=z+|UClf0yQ=qDXq493 z=D*s8n^Px>@7w*m(#A@Gq0y~T*NTI!_WGvlnVavR=sR_@MApW(IgMog$tW_eZnk!eRx zWvYChk*VC{Ydlr`%wNO%vXZ|;$_h`qOnoV*yE3n5>4jNq)qC$M{9^EN2%iT41YV(| diff --git a/modules/git/tests/repos/language_stats_repo/objects/8e/b563dc106e3dfd3ad0fa81f7a0c5e2604f80cd b/modules/git/tests/repos/language_stats_repo/objects/8e/b563dc106e3dfd3ad0fa81f7a0c5e2604f80cd deleted file mode 100644 index f89ecb7d607f53fa5bbeb8886a50a77b8ab0a69b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201 zcmV;)05<=40V^p=O;s>5Fl8__FfcPQQP4}zEJ-XWDauSLElDkAxV3ck2>vP({N3_wi~K4Q#UbJH&M5svLquvPp^Q%>jZOhpLgd}kvyYWtPiIB(kWkHYhnNd z3R#I|iMko7IXU?Zs}@QB@aa}&N$9mdVO>(;@X0l4Ce*y##LPUsvedkk{31P&4hARH zH^08^`h9+}+|tuGgk;Qax)!uT420MTH8RD=d~N+RuLDB&>mn~zO?oU`lfnl80FYD{ D5k*{W diff --git a/modules/git/tests/repos/language_stats_repo/objects/8f/ee858da5796dfb37704761701bb8e800ad9ef3 b/modules/git/tests/repos/language_stats_repo/objects/8f/ee858da5796dfb37704761701bb8e800ad9ef3 deleted file mode 100644 index 0219c2d565cdf6892d4633f715f275b2f5c600ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 828 zcmV-C1H=4y0iBe~&Z9;Ug|p@}C2#6@z2pmiqT0k7kAQwYAi$x~e#&o(ttU&XbNT-m9 zCtP#`kXksP7YIIki@O&584sA<{+-4xj+5V8g#QAJz_AQZbBqW`nxSbT{c#kd|L4y8 zd@OVD_0PzvW;nppJa9HNM+z<{^@mOX0+hb-mL*A6atR(vRi(!r%l5ZbLJ$8Cn1knR z=x7mxp3V=_<6vYmFDZHL|I`W4(?w8A)J~64Gqb5Pon&%`_GVYK%$VRLdfU$S%$P|) zAKS=l$i4nNMs*Z5cQMs?n*ha5W_i->Mr?>gxs}-+y_nIW#;4fM5wzwJaqnq4 zZ{T@bo%S!XN`%|=a7hAqxf|g`^31_`E=a=iJs{ni9v){;y!(|k&Dn#LrnoyX)ARX$ z*AexIPDwD~aVpJ2L;wM*75kPWuSybfo1NelmPW*EbZ0$J7UhPO4%tFqDMMQ*iv7gU zdWEHsJs_1nL5ToI=yQju3$^IoOJ3$n3W4t&z-<#Sf-QKLheSJ2w1mUrD=-ado6 zM79Zy>?+7b;ZQ7cWGOQHRngi8&sVJC54vHr`3X{7iK}4A#%Qa zdT4r?y*xPONEYl%L<0=%9XD~D{Ff%bx$W6@5)5}UMOJs0Tee>(vuHWYu%Pl>9LrBK# MrfWef0A_I#MnPW|jsO4v diff --git a/modules/git/tests/repos/language_stats_repo/objects/da/a5abe3c5f42cae598e362e8a8db6284565d6bb b/modules/git/tests/repos/language_stats_repo/objects/da/a5abe3c5f42cae598e362e8a8db6284565d6bb deleted file mode 100644 index 9d4d4b1a04fe12cba1b27260a5f9f935e1b05641..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 zcmV-G0Kflu0ZYosPf{>5V$jknOU+BkFVf3OEK5|#$;?YH%`7g_g$SmmaB1lkAVhN# WfzpZTskQ-?B^mj7TwDNKei)XuX&k8l diff --git a/modules/git/tests/repos/language_stats_repo/objects/pack/pack-371b1f6c24df14da4898b22c00ff8fb55303ac76.idx b/modules/git/tests/repos/language_stats_repo/objects/pack/pack-371b1f6c24df14da4898b22c00ff8fb55303ac76.idx new file mode 100644 index 0000000000000000000000000000000000000000..186136cb126104a88a2d5b178914b1c1dbc86f17 GIT binary patch literal 1520 zcmexg;-AdGz`z8=0|gj?79(SjTaYm`kWUU~0qP?MvjX*D!)!ot5N4+l<^Ylj^$n@L~4iL*%D*MH0Pj#qS4FW zr8!F5R$ zGWSUHQy<+;ZN_Kyr#q>>`Soqr@AHf0mY%*LBx82dwV>7G6w{Z=RYtS@S3Ia(9Tt`( zqTh4b>jZOhpLgd}kvyYWtPiIB(kWkHn{d;H@4#%U!|Uz`I!@O8QgZLmj8!Q<=4D>SRlRQ z1;g5TpQm09h}Ydz{Dk}Y{1(TAz;&%JU-7P5B>lsuTbU)H*ZzccNrl5F*QA-J4z2$? zW&NeM0_L$<-GzbD8>GagZ!KN@_~;j%b&-8$dR@KSG+a}!?S59Z?fsd3PbTes{r=VtGJ$26Jio9F_Ybw>)NS(qZ`DzcrY7O<6^|^kH*Z{o8Uv P9(ikby)sPtbZa62Y)$41 literal 0 HcmV?d00001 diff --git a/modules/git/tests/repos/language_stats_repo/objects/pack/pack-371b1f6c24df14da4898b22c00ff8fb55303ac76.pack b/modules/git/tests/repos/language_stats_repo/objects/pack/pack-371b1f6c24df14da4898b22c00ff8fb55303ac76.pack new file mode 100644 index 0000000000000000000000000000000000000000..046061c688b93c0f08c8aef52e1d1e5470de40cf GIT binary patch literal 2748 zcma)*Ss>Jl7sr2NCuVRhNx8^2X0djokllnzUO?;`J8iXjZ82A0B{_KaF!{7l<`EjvC3T7 zJSeLBQ#v9(PxVq3D8|t3g@CYE=P$A#&kU~izjpPrw3JuNmm(*x=8?QrF^`K$=1xv1 zc%?uKT(+Cft>di5PqMt%GwpQP!J-(?v z*{y{zv}}J1vyY$jF${WQ{XgrZEHH6*$m6Tz{$b(6Bv}-k2BZ~1lX?oSj=)(r zjv{`75CYY|uJ}O5C`~aDc%y6i0eh$R$JA^94(3eu^^=HVXr|%PFGUgX>m6bK6)6RdHIdFZjKT~lwwp!t$ zYEEs5!HesSEZkwGp#6s4O2K)Ms7J2W=HA!$%5rLOyOz?B>~3Pt)aS7d;kCqBclXWP zsIBeD-^X{}33NQX-fb6rhF{bWe5+O2mGH*WA!c?-@X86Tb(cRQOM=5f9Vsg<;oj!&7=(X*m3 z&l$Ybp@jL#xX#Xqyo)kmmn>hE^Bk^eR!I^>Zs$dCIez!X7*8xGz72Rj`d!dFcSD@xUXlryibvGDglEBU+!tsHuS5IxoN`wca z#6bJru<(O5$VEe5k`+wUl%C{Y8O_nIfB%`SG};)8R?C0F){$BnsH?0FywDr$`p&PMwKIa|Bv=EaRMI_=i0Wg7qCHXh1WvO~}w_9){M z@~b@#YdjN}M=_|XoE2bWUiKZWUdSq* zYiZg8<+nn*Or=|kErPu#0w=9J!X`J_4Sz0gYU_85;|>3KM?5f;(ro<3E$w1CkZX3_ z<`YV7l{sL#8)>#SAXKaoMNQ^7$%$)z@tVLJtaUk_C$`V`=GE%ye%$MsO4-rKTNQ@&M=A|M6=tL)5BW;KDGS_pAFbVH#dyq$G=5_Z zz3RNPiAh+$6)mEZIqjDs0zzwPTbB^WGfx&Djr1l&ss^opdG6(;b$ZHA=GCd3v)nI} z_nnVFIK6EnQxT2D^XFSSl|JXBfmyltT#m%Di`mNyzT&Yly_fAHG&P@QrMK=JQTWyS z&1^ADqepg)^%YtkUig@uRoh^C`jkTWc<*;=HrlY@O^<(1@pnCo5V3O*jbTDA&~1bZ z8(dKiNQn3KC(cR~I76usu-8+_wlhtfkKR_c^9)_37L2}JPOS-)`zZ&4Jp1l!6n*}x zKJ$a9cedVu$z15fhts|8bYHt!hk!Lcmm%|s2=}^+gh}VD+2m@`>4?CqX@L8xxlXEH zP;fpht^R-ywd)G$OI9{Gpyp&%E0*f6s?iT3+gKr{{CI3P@?$DGzaq)cs&s*LLmGM^hm4!OpV@*UPG#mj1ex zO$3u)LTRa&!{0_Cll1+9QCk9Ir-nCIzm1Rk*cO!UdOm<90X9)EW{7lGSd}Ex$Gsg=q~&8mEw0)w z>S@OYH=A#SdUL;n8rD{vS8i0(Y~934D7FwE^GX&1g?AoET>O37siM{7@YUou^^|?j z`O$#yJeC%qZ~Q>Yhsiy&(pVL5?<-Vj$)BWeCXy)tUdk{ zt@aP-!`k+1_N2g%42{+nnaV|FP?T{xtcyi2@7-YCms8;DV7}zLrhWZ!hER^-j+~px zXwsX=l%U{ljeNctm-#byJ&2h^#qmc+fW`)IF3d~18cZN*X{)PiY1s8+`rv3d6iT&H zMW7Kl0+zgT_>_2AX@z+BeRhNM(&t7dl82>`7fGg+5=2rC7iAV5;P(u@%)4j$txT z3<|~PS2Aip;qpsOjB(u||5BPj!sS}D*0cFJCEWaCM(KR%C&|T_V0E2}w!JUEjjU}Y z&t>Mf$tm0j9U>Qsd&+j%l&3-s=KYrn!p;b{cu0m-+#I|yog-!dICgnfGKgdLL**AaIpkMp#2yTn|T{Kh) z*wq1_F8`ta2e1knfke~j=wTWHid)y|-Y&&w3^p;4EKDTfO z1mrWs3mdrzBnK>(??(}!$TV(-K5>`i7}sPaYnN8{I1M63`vKlrrB98Do}N)vq0(Uk zwwpk5Tx?gm_G@6?O?>$8DgWua$C^$b`oYfYwmorcX}p%jmZTx{i6*EI%VqvXFo4%z zSg@X#K!T}QQz=$hR4*Ei#IFCafc))HId88LjS|mN$OI}H4(MWW6J2z@hf`@q^w#Z# zU*o!pwmq$p+=cdWQ`DI>rrX-r z@-Nh$A2uVfaIhYNh|8t0z5s{f sIV$%>Zh6euq{HyPe`_%FnzC!#o*g{p^>kaQuG6B;ywg|Kd}`_f04JCg+W-In literal 0 HcmV?d00001 diff --git a/modules/git/tests/repos/language_stats_repo/packed-refs b/modules/git/tests/repos/language_stats_repo/packed-refs new file mode 100644 index 0000000000..63e01583a4 --- /dev/null +++ b/modules/git/tests/repos/language_stats_repo/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +95d3505f2db273e40be79f84416051ae85e9ea0d refs/heads/master diff --git a/modules/git/tests/repos/language_stats_repo/refs/heads/.gitkeep b/modules/git/tests/repos/language_stats_repo/refs/heads/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/git/tests/repos/language_stats_repo/refs/heads/master b/modules/git/tests/repos/language_stats_repo/refs/heads/master deleted file mode 100644 index e89143e56b..0000000000 --- a/modules/git/tests/repos/language_stats_repo/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -341fca5b5ea3de596dc483e54c2db28633cd2f97 diff --git a/modules/git/tests/repos/language_stats_repo/refs/tags/.gitkeep b/modules/git/tests/repos/language_stats_repo/refs/tags/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From 4215476cee96ff471e4242387fec52c1df69d147 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Tue, 29 Apr 2025 13:31:36 +0000 Subject: [PATCH 059/115] [v11.0/forgejo] chore: tune down remote user promotion debug message shown as error (#7691) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7687 It is not an error for a remote user to not be promoted: this is the case for all users created via OAuth. Displaying an error is confusing to the admin when seen in the logs. Refs: https://codeberg.org/forgejo/forgejo/issues/7681 Co-authored-by: Earl Warren Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7691 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- services/remote/promote.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/remote/promote.go b/services/remote/promote.go index 26a54fb635..f37d00168c 100644 --- a/services/remote/promote.go +++ b/services/remote/promote.go @@ -98,7 +98,7 @@ func getRemoteUserToPromote(ctx context.Context, source *auth_model.Source, logi return nil, NewReason(log.ERROR, ReasonErrorLoginName, "getUserByLoginName('%s') %v", loginName, err), err } if len(users) == 0 { - return nil, NewReason(log.ERROR, ReasonLoginNameNotExists, "no user with LoginType UserTypeRemoteUser and LoginName '%s'", loginName), nil + return nil, NewReason(log.DEBUG, ReasonLoginNameNotExists, "no user with LoginType UserTypeRemoteUser and LoginName '%s'", loginName), nil } reason := ReasonNoSource From fe07c9063623624aa681c5c0ff2c360b84fb8bb7 Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Tue, 29 Apr 2025 18:10:53 +0000 Subject: [PATCH 060/115] [v11.0/forgejo] chore(release): next-digest moved to invisible.forgejo.org (#7723) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/7720 In order to improve the security of the Forgejo infrastructure the next-digest repository was moved to a private instance. ## Testing - After the merge, trigger a mirror to build a new v12.0-test release - Verify in experimental that the workflows works as expected - Verify v12.next.forgejo.org is upgraded with the latest commit - Once the test completes - Tag for backport to v11 & v7 - Manual backport to v7 because it conflicts Co-authored-by: Earl Warren Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7723 Reviewed-by: Earl Warren Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- .forgejo/workflows/publish-release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml index 0863a1597c..27d3b9383e 100644 --- a/.forgejo/workflows/publish-release.yml +++ b/.forgejo/workflows/publish-release.yml @@ -2,6 +2,8 @@ # # See also https://forgejo.org/docs/next/contributor/release/#stable-release-process # +# TOKEN_NEXT_DIGEST is a token with write repository access to https://invisible.forgejo.org/infrastructure/next-digest issued by https://invisible.forgejo.org/forgejo-next-digest +# # https://codeberg.org/forgejo-experimental/forgejo # # Copies a release from codeberg.org/forgejo-integration to codeberg.org/forgejo-experimental @@ -14,7 +16,7 @@ # vars.DOER: forgejo-experimental-ci # secrets.TOKEN: # -# http://private.forgejo.org/forgejo/forgejo +# http://invisible.forgejo.org/forgejo/forgejo # # Copies & sign a release from codeberg.org/forgejo-integration to codeberg.org/forgejo # @@ -80,7 +82,7 @@ jobs: - name: upgrade v*.next.forgejo.org uses: https://data.forgejo.org/infrastructure/next-digest@v1.1.0 with: - url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@code.forgejo.org/infrastructure/next-digest + url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@invisible.forgejo.org/infrastructure/next-digest ref_name: '${{ github.ref_name }}' image: 'codeberg.org/forgejo-experimental/forgejo' tag_suffix: '-rootless' From 6a9fb3dbbc50e57abc9bfd820be243f1db115d9a Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 30 Apr 2025 13:21:04 +0000 Subject: [PATCH 061/115] [v11.0/forgejo] chore: replace `github.com/go-testfixtures/testfixtures` (#7729) **Backport:** #7715 - Replaces `github.com/go-testfixtures/testfixtures` with a homebrew solution that is fully compatible. - The reason to replace this library is that it pulls in a lot of other libraries which is causing issues: (1) the test binary becomes bigger than necessary which really shows in incremental build times (this patch removes 27.6MiB of the integration test binary) (2) it pulls in libraries (mainly database drivers) that are not used and are not easy to upgrade in case of a security vulnerability, causing CI failures. (cherry picked from commit 32e64ccd3411b596f89e291e98c66f88b278b11f) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7729 Reviewed-by: Michael Kriese Co-authored-by: Gusted Co-committed-by: Gusted --- go.mod | 45 +- go.sum | 1535 +---------------------------- models/unittest/fixture_loader.go | 198 ++++ models/unittest/fixtures.go | 34 +- 4 files changed, 220 insertions(+), 1592 deletions(-) create mode 100644 models/unittest/fixture_loader.go diff --git a/go.mod b/go.mod index 61232b7b15..8a4cfbbc9b 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,6 @@ require ( github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-openapi/spec v0.20.14 github.com/go-sql-driver/mysql v1.9.1 - github.com/go-testfixtures/testfixtures/v3 v3.14.0 github.com/go-webauthn/webauthn v0.12.2 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f @@ -118,21 +117,11 @@ require ( ) require ( - cel.dev/expr v0.19.1 // indirect - cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.9.9 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/longrunning v0.6.1 // indirect - cloud.google.com/go/monitoring v1.21.1 // indirect - cloud.google.com/go/spanner v1.73.0 // indirect dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect github.com/DataDog/zstd v1.5.5 // indirect - github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/RoaringBitmap/roaring v1.9.3 // indirect github.com/andybalholm/brotli v1.1.1 // indirect @@ -164,7 +153,6 @@ require ( github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.8 // indirect - github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -173,11 +161,8 @@ require ( github.com/dlclark/regexp2 v1.11.4 // indirect github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/fatih/color v1.16.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect @@ -185,12 +170,10 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.7 // indirect - github.com/go-webauthn/x v0.1.19 // indirect + github.com/go-webauthn/x v0.1.20 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect @@ -201,10 +184,6 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-tpm v0.9.3 // indirect - github.com/google/s2a-go v0.1.8 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect - github.com/googleapis/go-sql-spanner v1.7.4 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -235,13 +214,13 @@ require ( github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rhysd/actionlint v1.6.27 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect @@ -252,18 +231,9 @@ require ( github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/blake3 v0.2.4 // indirect - go.etcd.io/bbolt v1.3.9 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.etcd.io/bbolt v1.4.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -271,11 +241,6 @@ require ( golang.org/x/mod v0.24.0 // indirect golang.org/x/time v0.10.0 // indirect golang.org/x/tools v0.31.0 // indirect - google.golang.org/api v0.203.0 // indirect - google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/grpc v1.71.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 16276ffb3a..aa81c2cbb7 100644 --- a/go.sum +++ b/go.sum @@ -1,615 +1,5 @@ -cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= -cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= -cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= -cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= -cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= -cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= -cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= -cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= -cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= -cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= -cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= -cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= -cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= -cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= -cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= -cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= -cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= -cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= -cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= -cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ= -cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= -cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= -cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= -cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= -cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= -cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= -cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= -cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= -cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= -cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= -cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= -cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= -cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= -cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= -cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= -cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= -cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= -cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= -cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= -cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= -cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= -cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= -cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= -cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= -cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= -cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= -cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= -cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= -cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= -cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= -cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= -cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= -cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= -cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= -cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= -cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= -cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= -cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/spanner v1.73.0 h1:0bab8QDn6MNj9lNK6XyGAVFhMlhMU2waePPa6GZNoi8= -cloud.google.com/go/spanner v1.73.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= -cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= -cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= -cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= code.forgejo.org/f3/gof3/v3 v3.10.6 h1:Ru/Iz+pqM8IPi7atUHE7+q7v3O3DRbYgMFqrFTsO1m8= code.forgejo.org/f3/gof3/v3 v3.10.6/go.mod h1:K6lQCWQIyN/5rjP/OJL9fMA6fd++satndE20w/I6Kss= code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU= @@ -644,11 +34,8 @@ connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk= connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA= @@ -659,23 +46,11 @@ github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= -github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= -github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo= -github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 h1:oVLqHXhnYtUwM89y9T1fXGaK9wTkXHgNp8/ZNMQzUxE= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= -github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= @@ -684,10 +59,6 @@ github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4 github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2 h1:cSXom2MoKJ9KPPw29RoZtHvUETY4F4n/kXl8m9btnQ0= github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2/go.mod h1:JitQWJ8JuV4Y87l8VsHiiwhb3cgdyn68mX40s7NT6PA= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= @@ -699,17 +70,12 @@ github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW5 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= -github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= -github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= @@ -757,7 +123,6 @@ github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH4 github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b h1:ju9Az5YgrzCeK3M1QwvZIpxYhChkXp7/L0RhDYsxXoE= github.com/blevesearch/zapx/v16 v16.1.9-0.20241217210638-a0519e7caf3b/go.mod h1:BlrYNpOu4BvVRslmIG+rLtKhmjIaRhIbG8sb9scGTwI= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -773,14 +138,8 @@ github.com/caddyserver/certmagic v0.22.2 h1:qzZURXlrxwR5m25/jpvVeEyJHeJJMvAwe5zl github.com/caddyserver/certmagic v0.22.2/go.mod h1:hbqE7BnkjhX5IJiFslPmrSeobSeZvI6ux8tyxhsd6qs= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ= @@ -788,30 +147,11 @@ github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdi github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -823,8 +163,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= -github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= -github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o= @@ -837,12 +175,10 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs= @@ -858,46 +194,18 @@ github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTe github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= +github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI= github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI= github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0= @@ -917,17 +225,8 @@ github.com/go-enry/go-enry/v2 v2.9.2 h1:giOQAtCgBX08kosrX818DCQJTCNtKwoPBGu0qb6n github.com/go-enry/go-enry/v2 v2.9.2/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= -github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= -github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= -github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= -github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= @@ -936,20 +235,10 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= @@ -958,25 +247,20 @@ github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/ github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= -github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-testfixtures/testfixtures/v3 v3.14.0 h1:aRt5qyH2XjzFgCC5NizNs6QrzjO7rC4pQZ1oJpPIdo8= -github.com/go-testfixtures/testfixtures/v3 v3.14.0/go.mod h1:HHb6Yd8spzm6aFZU6jwBj9qFvVUNNkx5nGbjG4UHeOE= github.com/go-webauthn/webauthn v0.12.2 h1:yLaNPgBUEXDQtWnOjhsGhMMCEWbXwjg/aNkC8riJQI8= github.com/go-webauthn/webauthn v0.12.2/go.mod h1:Q8SZPPj4sZ469fNTcQXxRpzJOdb30jQrn/36FX8jilA= -github.com/go-webauthn/x v0.1.19 h1:IUfdHiBRoTdujpBA/14qbrMXQ3LGzYe/PRGWdZcmudg= -github.com/go-webauthn/x v0.1.19/go.mod h1:C5arLuTQ3pVHKPw89v7CDGnqAZSZJj+4Jnr40dsn7tk= +github.com/go-webauthn/x v0.1.20 h1:brEBDqfiPtNNCdS/peu8gARtq8fIPsHz0VzpPjGvgiw= +github.com/go-webauthn/x v0.1.20/go.mod h1:n/gAc8ssZJGATM0qThE+W+vfgXiMedsWi3wf/C4lld0= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs= @@ -987,77 +271,32 @@ github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQg github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= -github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= -github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -1070,62 +309,13 @@ github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20241017200806-017d972448fc h1:NGyrhhFhwvRAZg02jnYVg3GBQy0qGBKmFQJwaPmpmxs= github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= -github.com/googleapis/go-sql-spanner v1.7.4 h1:pwndJlqgIMOewkORveYQQocaSyOGqaQg8e2Os8hYh00= -github.com/googleapis/go-sql-spanner v1.7.4/go.mod h1:DfuJMbqpcDQwtbol+TnfO+AUyeoW5H+w8Gm216dTPys= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= @@ -1140,17 +330,12 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -1158,62 +343,32 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= -github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhillyerd/enmime/v2 v2.1.0 h1:c8Qwi5Xq5EdtMN6byQWoZ/8I2RMTo6OJ7Xay+s1oPO0= github.com/jhillyerd/enmime/v2 v2.1.0/go.mod h1:EJ74dcRbBcqHSP2TBu08XRoy6y3Yx0cevwb1YkGMEmQ= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -1232,9 +387,6 @@ github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8= github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI= github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= @@ -1244,14 +396,12 @@ github.com/markbates/goth v1.80.0 h1:NnvatczZDzOs1hn9Ug+dVYf2Viwwkp/ZDX5K+GLjan8 github.com/markbates/goth v1.80.0/go.mod h1:4/GYHo+W6NWisrMPZnq0Yr2Q70UntNLn7KXEFhrIdAY= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY= @@ -1262,8 +412,6 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY= github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= @@ -1312,25 +460,14 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= -github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= -github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1338,9 +475,6 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= @@ -1358,30 +492,21 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= -github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= -github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1389,10 +514,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1402,13 +523,11 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= @@ -1433,12 +552,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= @@ -1451,41 +565,10 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= gitlab.com/gitlab-org/api/client-go v0.126.0 h1:VV5TdkF6pMbEdFGvbR2CwEgJwg6qdg1u3bj5eD2tiWk= gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -1500,15 +583,10 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= @@ -1516,132 +594,28 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= @@ -1649,52 +623,12 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -1702,92 +636,27 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -1798,12 +667,7 @@ golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= @@ -1811,21 +675,11 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -1833,76 +687,13 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= @@ -1911,283 +702,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= -google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= @@ -2209,7 +729,6 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -2217,70 +736,28 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE= modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU= diff --git a/models/unittest/fixture_loader.go b/models/unittest/fixture_loader.go new file mode 100644 index 0000000000..67ef1b28df --- /dev/null +++ b/models/unittest/fixture_loader.go @@ -0,0 +1,198 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package unittest + +import ( + "database/sql" + "encoding/hex" + "encoding/json" //nolint:depguard + "fmt" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +type insertSQL struct { + statement string + values []any +} + +type fixtureFile struct { + name string + insertSQLs []insertSQL +} + +type loader struct { + db *sql.DB + dialect string + + fixtureFiles []*fixtureFile +} + +func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string) (*loader, error) { + l := &loader{ + db: db, + dialect: dialect, + fixtureFiles: []*fixtureFile{}, + } + + // Load fixtures + for _, fixturePath := range fixturePaths { + stat, err := os.Stat(fixturePath) + if err != nil { + return nil, err + } + + // If fixture path is a directory, then read read the files of the directory + // and use those as fixture files. + if stat.IsDir() { + files, err := os.ReadDir(fixturePath) + if err != nil { + return nil, err + } + for _, file := range files { + if !file.IsDir() { + fixtureFile, err := l.buildFixtureFile(filepath.Join(fixturePath, file.Name())) + if err != nil { + return nil, err + } + l.fixtureFiles = append(l.fixtureFiles, fixtureFile) + } + } + } else { + fixtureFile, err := l.buildFixtureFile(fixturePath) + if err != nil { + return nil, err + } + l.fixtureFiles = append(l.fixtureFiles, fixtureFile) + } + } + + return l, nil +} + +// quoteKeyword returns the quoted string of keyword. +func (l *loader) quoteKeyword(keyword string) string { + switch l.dialect { + case "sqlite3": + return `"` + keyword + `"` + case "mysql": + return "`" + keyword + "`" + case "postgres": + parts := strings.Split(keyword, ".") + for i, p := range parts { + parts[i] = `"` + p + `"` + } + return strings.Join(parts, ".") + default: + return "invalid" + } +} + +// placeholder returns the placeholder string. +func (l *loader) placeholder(index int) string { + if l.dialect == "postgres" { + return fmt.Sprintf("$%d", index) + } + return "?" +} + +func (l *loader) buildFixtureFile(fixturePath string) (*fixtureFile, error) { + f, err := os.Open(fixturePath) + if err != nil { + return nil, err + } + defer f.Close() + + var records []map[string]any + if err := yaml.NewDecoder(f).Decode(&records); err != nil { + return nil, err + } + + fixture := &fixtureFile{ + name: filepath.Base(strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))), + insertSQLs: []insertSQL{}, + } + + for _, record := range records { + columns := []string{} + sqlValues := []string{} + values := []any{} + i := 1 + + for key, value := range record { + columns = append(columns, l.quoteKeyword(key)) + + switch v := value.(type) { + case string: + // Try to decode hex. + if strings.HasPrefix(v, "0x") { + value, err = hex.DecodeString(strings.TrimPrefix(v, "0x")) + if err != nil { + return nil, err + } + } + case []any: + // Decode array. + var bytes []byte + bytes, err = json.Marshal(v) + if err != nil { + return nil, err + } + value = string(bytes) + } + + values = append(values, value) + + sqlValues = append(sqlValues, l.placeholder(i)) + i++ + } + + // Construct the insert SQL. + fixture.insertSQLs = append(fixture.insertSQLs, insertSQL{ + statement: fmt.Sprintf( + "INSERT INTO %s (%s) VALUES (%s)", + l.quoteKeyword(fixture.name), + strings.Join(columns, ", "), + strings.Join(sqlValues, ", "), + ), + values: values, + }) + } + + return fixture, nil +} + +func (l *loader) Load() error { + // Start transaction. + tx, err := l.db.Begin() + if err != nil { + return err + } + + defer func() { + _ = tx.Rollback() + }() + + // Clean the table and re-insert the fixtures. + tableDeleted := map[string]struct{}{} + for _, fixture := range l.fixtureFiles { + if _, ok := tableDeleted[fixture.name]; !ok { + if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s", l.quoteKeyword(fixture.name))); err != nil { + return fmt.Errorf("cannot delete table %s: %w", fixture.name, err) + } + tableDeleted[fixture.name] = struct{}{} + } + + for _, insertSQL := range fixture.insertSQLs { + if _, err := tx.Exec(insertSQL.statement, insertSQL.values...); err != nil { + return fmt.Errorf("cannot insert %q with values %q: %w", insertSQL.statement, insertSQL.values, err) + } + } + } + + return tx.Commit() +} diff --git a/models/unittest/fixtures.go b/models/unittest/fixtures.go index 329071ce28..495f9a2aac 100644 --- a/models/unittest/fixtures.go +++ b/models/unittest/fixtures.go @@ -6,7 +6,6 @@ package unittest import ( "fmt" - "os" "path/filepath" "time" @@ -14,12 +13,11 @@ import ( "forgejo.org/modules/auth/password/hash" "forgejo.org/modules/setting" - "github.com/go-testfixtures/testfixtures/v3" "xorm.io/xorm" "xorm.io/xorm/schemas" ) -var fixturesLoader *testfixtures.Loader +var fixturesLoader *loader // GetXORMEngine gets the XORM engine func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) { @@ -31,6 +29,7 @@ func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) { func OverrideFixtures(dir string) func() { old := fixturesLoader + opts := FixturesOptions{ Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), Base: setting.AppWorkPath, @@ -39,6 +38,7 @@ func OverrideFixtures(dir string) func() { if err := InitFixtures(opts); err != nil { panic(err) } + return func() { fixturesLoader = old } @@ -47,19 +47,19 @@ func OverrideFixtures(dir string) func() { // InitFixtures initialize test fixtures for a test database func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { e := GetXORMEngine(engine...) - var fixtureOptionFiles func(*testfixtures.Loader) error + fixturePaths := []string{} if opts.Dir != "" { - fixtureOptionFiles = testfixtures.Directory(opts.Dir) + fixturePaths = append(fixturePaths, opts.Dir) } else { - fixtureOptionFiles = testfixtures.Files(opts.Files...) + fixturePaths = append(fixturePaths, opts.Files...) } - var fixtureOptionDirs []func(*testfixtures.Loader) error if opts.Dirs != nil { for _, dir := range opts.Dirs { - fixtureOptionDirs = append(fixtureOptionDirs, testfixtures.Directory(filepath.Join(opts.Base, dir))) + fixturePaths = append(fixturePaths, filepath.Join(opts.Base, dir)) } } - dialect := "unknown" + + var dialect string switch e.Dialect().URI().DBType { case schemas.POSTGRES: dialect = "postgres" @@ -68,22 +68,10 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { case schemas.SQLITE: dialect = "sqlite3" default: - fmt.Println("Unsupported RDBMS for integration tests") - os.Exit(1) - } - loaderOptions := []func(loader *testfixtures.Loader) error{ - testfixtures.Database(e.DB().DB), - testfixtures.Dialect(dialect), - testfixtures.DangerousSkipTestDatabaseCheck(), - fixtureOptionFiles, - } - loaderOptions = append(loaderOptions, fixtureOptionDirs...) - - if e.Dialect().URI().DBType == schemas.POSTGRES { - loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences()) + panic("Unsupported RDBMS for test") } - fixturesLoader, err = testfixtures.New(loaderOptions...) + fixturesLoader, err = newFixtureLoader(e.DB().DB, dialect, fixturePaths) if err != nil { return err } From 44a5cd3b7abc4f50c175d81302e581543d917c94 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Thu, 1 May 2025 15:36:39 +0500 Subject: [PATCH 062/115] [v11.0/forgejo] i18n: update of translations from Codeberg Translate Translation updates that are applicable to v11 strings were picked from this commit: 4fe172e4b24cc275f0d15b2ec133ccfe577b6d8a Changes to strings that are only present in the v12 branch were not picked. Below is a list of co-authors of the ported commit. It may contain co-authors who's changes were not picked due to being v12-only. Co-authored-by: Caesar Schinas Co-authored-by: Codeberg Translate Co-authored-by: Edgarsons Co-authored-by: Gusted Co-authored-by: Miguel P.L Co-authored-by: PeterDaveHello Co-authored-by: SomeTr Co-authored-by: Wuzzy Co-authored-by: antaanimosity Co-authored-by: artnay Co-authored-by: earl-warren Co-authored-by: justbispo Co-authored-by: lucasmz.dev Co-authored-by: oscarotero Co-authored-by: otf31 Co-authored-by: themandalorian Co-authored-by: xtex Co-authored-by: zerica Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/fi/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/pt_BR/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/pt_PT/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/zh_Hans/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/de/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/es/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/fr/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/gl/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/lv/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/nl/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/pt_BR/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/pt_PT/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/uk/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/zh_Hans/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/zh_Hant/ Translation: Forgejo/forgejo Translation: Forgejo/forgejo-next --- options/locale/locale_es-ES.ini | 63 ++++++++++++++++++--------- options/locale/locale_fr-FR.ini | 2 +- options/locale/locale_lv-LV.ini | 2 +- options/locale/locale_pt-BR.ini | 2 +- options/locale/locale_pt-PT.ini | 2 +- options/locale/locale_uk-UA.ini | 2 +- options/locale/locale_zh-CN.ini | 60 ++++++++++++------------- options/locale/locale_zh-TW.ini | 2 + options/locale_next/locale_fi-FI.json | 6 ++- options/locale_next/locale_pt-BR.json | 3 +- options/locale_next/locale_pt-PT.json | 3 +- 11 files changed, 87 insertions(+), 60 deletions(-) diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 3ff6c565b7..70a0d7e23a 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -38,12 +38,12 @@ passcode=Código de acceso webauthn_insert_key=Introduzca su clave de seguridad webauthn_sign_in=Presione el botón en su clave de seguridad. Si su clave de seguridad no tiene ningún botón, vuelva a insertarla. -webauthn_press_button=Por favor, presione el botón de su llave de seguridad… +webauthn_press_button=Por favor, presione el botón en su clave de seguridad… webauthn_use_twofa=Utilice un código de doble factor desde su teléfono móvil webauthn_error=No se pudo leer su llave de seguridad. webauthn_unsupported_browser=Su navegador no soporta actualmente WebAuthn. webauthn_error_unknown=Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo. -webauthn_error_insecure=`WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1"` +webauthn_error_insecure=WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1" webauthn_error_unable_to_process=El servidor no pudo procesar su solicitud. webauthn_error_duplicated=La clave de seguridad no está permitida para esta solicitud. Por favor, asegúrese de que la clave no está ya registrada. webauthn_error_empty=Debe establecer un nombre para esta clave. @@ -72,7 +72,7 @@ all=Todos sources=Propios mirrors=Réplica collaborative=Colaborativo -forks=Forks +forks=Bifurcaciones activities=Actividades pull_requests=Solicitudes de incorporación de cambios @@ -301,7 +301,7 @@ offline_mode.description=Deshabilitar redes de distribución de contenido de ter disable_gravatar=Desactivar Gravatar disable_gravatar.description=Desactivar el Gravatar y otros fuentes de avatares de terceros. Se utilizará un avatar por defecto a menos que un usuario suba un avatar localmente. federated_avatar_lookup=Habilitar avatares federados -federated_avatar_lookup.description=Buscar de avatares con Libravatar. +federated_avatar_lookup.description=Busca avatares con Libravatar. disable_registration=Deshabilitar auto-registro disable_registration.description=Sólo los administradores de la instancia podrán crear nuevas cuentas. Es muy recomendable mantener deshabilitado el registro a menos que pretenda alojar una instancia pública para todo el mundo y esté preparado para lidiar con grandes cantidades de cuentas de spam. allow_only_external_registration.description=Los usuarios sólo podrán crear nuevas cuentas utilizando servicios externos configurados. @@ -669,7 +669,7 @@ still_own_packages=Tu cuenta posee uno o más paquetes, elimínalos primero. org_still_own_repo=Esta organización todavía posee uno o más repositorios, elimínalos o transfiérelos primero. org_still_own_packages=Esta organización todavía posee uno o más paquetes, elimínalos primero. -target_branch_not_exist=La rama de destino no existe +target_branch_not_exist=La rama de destino no existe. admin_cannot_delete_self = No puedes eliminarte a ti mismo cuando eres un admin (administrador). Por favor, elimina primero tus privilegios de administrador. username_error_no_dots = ` solo puede contener carácteres alfanuméricos ("0-9","a-z","A-Z"), guiones ("-"), y guiones bajos ("_"). No puede empezar o terminar con carácteres no alfanuméricos y también están prohibidos los carácteres no alfanuméricos consecutivos.` unsupported_login_type = No se admite el tipo de inicio de sesión para eliminar la cuenta. @@ -844,7 +844,7 @@ add_email_success=La nueva dirección de correo electrónico ha sido añadida. email_preference_set_success=La preferencia de correo electrónico se ha establecido correctamente. add_openid_success=La nueva dirección OpenID ha sido añadida. keep_email_private=Ocultar dirección de correo electrónico -keep_email_private_popup=Esto ocultará tu dirección de correo electrónico de tu perfil. Ya no será la dirección predeterminada para los confirmaciones realizadas a través de la interfaz web, como las subidas y ediciones de archivos, y no se utilizará para las confirmaciones de fusión. En su lugar, se utilizará una dirección especial %s para asociar las confirmaciones a tu cuenta. Ten en cuenta que cambiar esta opción no afectará a las confirmaciones existentes. +keep_email_private_popup=Su dirección de correo electrónico no se mostrará en su perfil y no será la predeterminada para las confirmaciones realizadas a través de la interfaz web, como las subidas de archivos, las ediciones y las confirmaciones de fusión. En su lugar, se utilizará una dirección especial %s para vincular las confirmaciones a tu cuenta. Esta opción no afectará a las confirmaciones existentes. openid_desc=OpenID le permite delegar la autenticación a un proveedor externo. manage_ssh_keys=Gestionar claves SSH @@ -1084,6 +1084,10 @@ quota.rule.exceeded = Excedido quota.rule.no_limit = Ilimitado quota.sizes.assets.all = Activos +access_token_regeneration = Regenerar token de acceso +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.attachments.issues = Archivos adjuntos de incidencia + [repo] owner=Propietario owner_helper=Algunas organizaciones pueden no aparecer en el menú desplegable debido a un límite máximo de recuento de repositorios. @@ -1518,7 +1522,7 @@ issues.new.no_projects=Ningún proyecto issues.new.open_projects=Proyectos abiertos issues.new.closed_projects=Proyectos cerrados issues.new.no_items=No hay elementos -issues.new.milestone=Milestone +issues.new.milestone=Hito issues.new.no_milestone=Sin hito issues.new.clear_milestone=Limpiar Milestone issues.new.open_milestone=Hitos abiertos @@ -1566,12 +1570,12 @@ issues.change_title_at=`cambió el título de %s a %s issues.change_ref_at=`cambió referencia de %s a %s %s` issues.remove_ref_at=`eliminó la referencia %s %s` issues.add_ref_at=`añadió la referencia %s %s` -issues.delete_branch_at=`rama eliminada %s %s` +issues.delete_branch_at=`eliminó la rama %s %s` issues.filter_label=Etiqueta issues.filter_label_exclude=`Usa alt + clic/enter para excluir etiquetas` issues.filter_label_no_select=Todas las etiquetas issues.filter_label_select_no_label=Sin etiqueta -issues.filter_milestone=Milestone +issues.filter_milestone=Hito issues.filter_milestone_all=Todos los hitos issues.filter_milestone_none=Sin hitos issues.filter_milestone_open=Abrir hitos @@ -1973,7 +1977,7 @@ pulls.auto_merge_canceled_schedule_comment=`canceló la fusión automática de e pulls.delete.title=¿Borrar este pull request? pulls.delete.text=¿Realmente quieres eliminar esta pull request? (Esto eliminará permanentemente todo el contenido. Considera cerrarlo si simplemente deseas archivarlo) -pulls.recently_pushed_new_branches=Has realizado push en la rama %[1]s %[2]s +pulls.recently_pushed_new_branches=Empujaste en la rama %[1]s %[2]s pull.deleted_branch=(eliminado):%s @@ -1984,7 +1988,7 @@ milestones.no_due_date=Sin fecha límite milestones.open=Abrir milestones.close=Cerrar milestones.new_subheader=Los hitos pueden ayudarle a organizar los problemas y monitorizar su progreso. -milestones.completeness=%d%% Completado +milestones.completeness=%d%% Completado milestones.create=Crear hito milestones.title=Título milestones.desc=Descripción @@ -2025,7 +2029,7 @@ ext_wiki=Wiki externa ext_wiki.desc=Enlace a una wiki externa. wiki=Wiki -wiki.welcome=¡Bienvenidos a la Wiki! +wiki.welcome=Bienvenido a la Wiki. wiki.welcome_desc=Esta wiki le permite escribir y compartir documentación con otros colaboradores. wiki.desc=Escriba y comparta documentación con colaboradores. wiki.create_first_page=Crear la primera página @@ -2334,7 +2338,7 @@ settings.event_create=Crear settings.event_create_desc=Rama o etiqueta creada. settings.event_delete=Eliminar settings.event_delete_desc=Rama o etiqueta eliminada. -settings.event_fork=Fork +settings.event_fork=Bifurcación settings.event_fork_desc=Repositorio forkeado. settings.event_wiki=Wiki settings.event_wiki_desc=Página de la Wiki creada, renombrada, editada o eliminada. @@ -2522,7 +2526,7 @@ settings.archive.branchsettings_unavailable=Los ajustes de rama no están dispon settings.archive.tagsettings_unavailable=Los ajustes de las etiquetas no están disponibles si el repositorio está archivado. settings.unarchive.button=Desarchivar repositorio settings.unarchive.header=Desarchivar este repositorio -settings.unarchive.text=La desarchivación del repositorio restablecerá su capacidad de recibir confirmaciones y subidos, así como nuevas incidencias y solicitudes de incorporación de cambios. +settings.unarchive.text=La desarchivación del repositorio restablecerá su capacidad de recibir confirmaciones y empujes, así como nuevas incidencias y solicitudes de incorporación de cambios. settings.unarchive.success=El repositorio se ha desarchivado correctamente. settings.unarchive.error=Ocurrió un error mientras se trataba de des-archivar el repositorio. Revisa el registro para más detalles. settings.update_avatar_success=El avatar del repositorio ha sido actualizado. @@ -2645,7 +2649,7 @@ release.cancel=Cancelar release.publish=Publicar lanzamiento release.save_draft=Guardar borrador release.edit_release=Actualizar Lanzamiento -release.delete_release=Eliminar Lanzamiento +release.delete_release=Eliminar lanzamiento release.delete_tag=Eliminar tag release.deletion=Eliminar lanzamiento release.deletion_desc=Eliminar un lanzamiento sólo lo elimina de Forgejo. No afectará la etiqueta Git, el contenido de su repositorio o su historial. ¿Continuar? @@ -2874,6 +2878,11 @@ issues.filter_no_results = No hay resultados release.type_attachment = Archivo adjunto +issues.reaction.alt_few = %[1]s reaccionado con %[2]s. +settings.event_pull_request_enforcement = Aplicación +settings.sourcehut_builds.visibility = Visibilidad de trabajo +settings.ignore_stale_approvals = Ignorar las aprobaciones obsoletas + [graphs] component_loading = Cargando %s… component_loading_failed = No se pudo cargar %s @@ -2940,11 +2949,11 @@ settings.hooks_desc=Añadir webhooks que serán ejecutados para todos lo settings.labels_desc=Añadir etiquetas que pueden ser utilizadas en problemas para todos los repositorios bajo esta organización. -members.membership_visibility=Visibilidad de Membresía: +members.membership_visibility=Visibilidad de membresía: members.public=Público -members.public_helper=hacer oculto +members.public_helper=Hacer oculto members.private=Oculto -members.private_helper=hacer público +members.private_helper=Hacer público members.member_role=Rol del miembro: members.owner=Propietario members.member=Miembro @@ -2953,7 +2962,7 @@ members.remove.detail=¿Destituir a %[1]s de %[2]s? members.leave=Abandonar members.leave.detail=¿Irse de %s? members.invite_desc=Añadir un miembro nuevo a %s: -members.invite_now=Invitar +members.invite_now=Invitar ahora teams.join=Unirse teams.leave=Abandonar @@ -2962,7 +2971,7 @@ teams.can_create_org_repo=Crear repositorios teams.can_create_org_repo_helper=Los miembros pueden crear nuevos repositorios en la organización. El creador obtendrá acceso al administrador del nuevo repositorio. teams.none_access=Sin acceso teams.none_access_helper=Los miembros no pueden ver o hacer ninguna otra acción en esta unidad. -teams.general_access=Acceso general +teams.general_access=Acceso personalizado teams.general_access_helper=Los permisos de los miembros se decidirán por debajo de la tabla de permisos. teams.read_access=Leer teams.read_access_helper=Los miembros pueden ver y clonar los repositorios del equipo. @@ -3552,6 +3561,10 @@ emails.delete_desc = ¿Estás seguro que quieres eliminar esta dirección de cor monitor.duration = Duración (es) +self_check = Autocomprobación +dashboard.sync_tag.started = Sincronización de etiquetas iniciada +config.app_slogan = Eslogan de la instancia + [action] create_repo=creó el repositorio %s rename_repo=repositorio renombrado de %[1]s a %[3]s @@ -3814,6 +3827,11 @@ alt.repository.multiple_groups = Este paquete está disponible en múltiples gru arch.version.description = Descripción arch.version.provides = Proveedores +arch.version.optdepends = Dependencias opcionales +arch.version.makedepends = Construir dependencias +arch.version.checkdepends = Comprobar dependencias +npm.dependencies.bundle = Empaquetar dependencias + [secrets] secrets=Secretos description=Los secretos pasarán a ciertas acciones y no se podrán leer de otro modo. @@ -3920,6 +3938,9 @@ runs.expire_log_message = Los registros han sido eliminados porque eran demasiad runs.workflow = Flujo de trabajo +workflow.dispatch.use_from = Usar el flujo de trabajo de +workflow.dispatch.run = Correr flujo de trabajo + [projects] type-1.display_name=Proyecto individual type-2.display_name=Proyecto de repositorio @@ -3961,7 +3982,7 @@ exact = Exacto exact_tooltip = Incluir sólo los resultados que corresponden al término de búsqueda exacto issue_kind = Buscar incidencias… fuzzy = Difusa -runner_kind = Buscar ejecutores… +runner_kind = Buscar corredores… regexp_tooltip = Interpretar los términos de búsqueda como una expresión regular regexp = Expresión Regular diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index ef4b7bad5d..a319eb9948 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -4080,4 +4080,4 @@ issues.write = Écrire : Fermer des tickets et gérer les métadonnées t pulls.read = Lire : Lire et créer des demandes de tirage. [translation_meta] -test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce doux 100 % de complétion :) +test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce doux 100 % de complétion. :-) diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 70ce31ec4f..3d3f4723d4 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -228,7 +228,7 @@ server_internal = Iekšēja servera kļūda app_desc=Pašmitināms Git pakalpojums bez galvassāpēm install=Viegli uzstādīt install_desc=Vienkārši jāpalaiž izpildāmā datne vajadzīgajai sistēmai, jāizmanto Docker vai jāiegūst pakotne. -platform=Pieejama dažādām platformām +platform=Dažādas platformas lightweight=Viegla lightweight_desc=Forgejo ir zemas tehniskās prasības, un to var darbināt nedārgā Raspberry Pi datorā. Taupām savas ierīces patērēto enerģiju! license=Atvērtā pirmkoda diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 92fa50d618..d36b529972 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -1405,7 +1405,7 @@ editor.file_is_a_symlink=`"%s" é um link simbólico. Links simbólicos não pod editor.filename_is_a_directory=O nome do arquivo "%s" já é usado como um nome de diretório neste repositório. editor.file_editing_no_longer_exists=O arquivo que está sendo editado, "%s", não existe mais neste repositório. editor.file_deleting_no_longer_exists=O arquivo a ser excluído, "%s", não existe mais neste repositório. -editor.file_changed_while_editing=O conteúdo do arquivo mudou desde que você começou a editar. Clique aqui para ver as diferenças ou clique em Aplicar commit das alterações novamente para sobrescrever as alterações com sua versão atual. +editor.file_changed_while_editing=O conteúdo do arquivo mudou desde que você abriu o arquivo. Clique aqui para ver as diferenças ou clique em Aplicar commit das alterações novamente para sobrescrever as alterações com sua versão atual. editor.file_already_exists=Um arquivo com nome "%s" já existe neste repositório. editor.commit_empty_file_header=Fazer commit de um arquivo vazio editor.commit_empty_file_text=O arquivo que você está prestes fazer commit está vazio. Continuar? diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 05613030b5..c7798d4cdf 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1418,7 +1418,7 @@ editor.file_is_a_symlink=`"%s" é uma ligação simbólica. Ligações simbólic editor.filename_is_a_directory=O nome de ficheiro "%s" já está a ser usado como um nome de pasta neste repositório. editor.file_editing_no_longer_exists=O ficheiro que está a ser editado, "%s", já não existe neste repositório. editor.file_deleting_no_longer_exists=O ficheiro que está a ser eliminado, "%s", já não existe neste repositório. -editor.file_changed_while_editing=O conteúdo do ficheiro mudou desde que começou a editar. Clique aqui para ver as modificações ou clique em Cometer modificações novamente para escrever por cima. +editor.file_changed_while_editing=O conteúdo do ficheiro mudou desde que abriu o ficheiro. Clique aqui para ver as modificações ou Cometer modificações novamente para escrever por cima. editor.file_already_exists=Já existe um ficheiro com o nome "%s" neste repositório. editor.commit_empty_file_header=Cometer um ficheiro vazio editor.commit_empty_file_text=O ficheiro que está prestes a cometer está vazio. Quer continuar? diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 7d3d6f4c8b..253022e54c 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -231,7 +231,7 @@ platform_desc=Forgejo підтверджено працює на вільних lightweight=Невибагливість lightweight_desc=Forgejo має низькі вимоги до ресурсів та може працювати на недорогому Raspberry Pi. Заощадьте енергію свого комп'ютера! license=Відкритий вихідний код -license_desc=Відвідайте Forgejo! Приєднайтесь до нас та зробіть свій внесок до проєкту, щоб зробити його ще краще. Не бійтеся долучитися! +license_desc=Відвідайте Forgejo! Приєднуйтесь до нас та зробіть свій внесок, щоб покращити проєкт ще більше. Не бійтеся долучитися! install_desc = Просто запустіть уже зібрану програму для своєї платформи, розгорніть її за допомогою Docker або встановіть пакунок. [install] diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 2e062d80b4..2b0c767f77 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -322,12 +322,12 @@ install_btn_confirm=立即安装 test_git_failed=无法识别 “git” 命令:%v sqlite3_not_available=当前 Forgejo 版本不支持 SQLite3。请从 %s 下载官方构建版(注:请勿下载标有 “gobuild” 的版本)。 invalid_db_setting=数据库设置无效:%v -invalid_db_table=数据库表 '%s' 无效: %v +invalid_db_table=数据库表 '%s' 无效:%v invalid_repo_path=仓库根目录设置无效:%v -invalid_app_data_path=应用数据路径无效: %v +invalid_app_data_path=应用数据路径无效:%v run_user_not_match=运行用户名不是当前的用户名:%s -> %s -internal_token_failed=生成内部令牌失败: %v -secret_key_failed=生成密钥失败: %v +internal_token_failed=生成内部令牌失败:%v +secret_key_failed=生成密钥失败:%v save_config_failed=应用配置保存失败:%v invalid_admin_setting=管理员帐户设置无效:%v invalid_log_root_path=日志路径无效:%v @@ -479,7 +479,7 @@ password_pwned_err=无法完成对 HaveIBeenPwned 的请求 last_admin=您不能删除最后一个管理员。必须至少保留一个管理员。 change_unconfirmed_email = 如果您在注册时提供了错误的邮箱地址,您可以在下方修改,激活邮件会发送到修改后的邮箱地址。 change_unconfirmed_email_summary = 修改用来接收激活邮件的邮箱地址。 -change_unconfirmed_email_error = 无法修改邮箱地址: %v +change_unconfirmed_email_error = 无法修改邮箱地址:%v tab_signin = 登录 tab_signup = 注册 hint_login = 已经有账户了吗?立即登录! @@ -537,7 +537,7 @@ issue.in_tree_path=在 %s 中: release.new.subject=%[2]s 中的 %[1]s 发布了 release.new.text=@%[1]s 于 %[3]s 发布了 %[2]s -release.title=标题: %s +release.title=标题:%s release.note=注释: release.downloads=下载: release.download.zip=源代码(ZIP) @@ -617,7 +617,7 @@ include_error=`必须包含子字符串 "%s"。` glob_pattern_error=`匹配模式无效:%s.` regex_pattern_error=`正则表达式无效:%s.` username_error=` 只允许包含字母数字字符(“0-9”、“a-z”、“A-Z”)、破折号(“-”)、下划线(“_”)和点(“.”)。不能以非字母数字字符开头或结尾,并且不允许连续的非字母数字字符。` -invalid_group_team_map_error=`映射无效: %s` +invalid_group_team_map_error=`映射无效:%s` unknown_error=未知错误: captcha_incorrect=验证码不正确。 password_not_match=密码不匹配。 @@ -659,7 +659,7 @@ organization_leave_success=您已成功离开组织 %s。 invalid_ssh_key=无法验证您的 SSH 密钥:%s invalid_gpg_key=无法验证您的 GPG 密钥:%s -invalid_ssh_principal=无效的规则: %s +invalid_ssh_principal=无效的规则:%s must_use_public_key=您提供的密钥是私钥。不要在任何地方上传您的私钥,请改用您的公钥。 unable_verify_ssh_key=无法验证 SSH 密钥,请仔细检查是否有错误。 auth_failed=授权验证失败:%v @@ -1459,7 +1459,7 @@ commits.view_path=在历史记录中的此处查看 commit.operations=操作 commit.revert=还原 -commit.revert-header=还原: %s +commit.revert-header=还原:%s commit.revert-content=选择要还原的分支: commit.cherry-pick=拣选 commit.cherry-pick-header=Cherry-pick:%s @@ -2279,7 +2279,7 @@ settings.trust_model.collaborator=协作者 settings.trust_model.collaborator.long=协作者:信任协作者的签名 settings.trust_model.collaborator.desc=此仓库中协作者的有效签名将被标记为「可信」(无论它们是否是提交者),签名只符合提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。 settings.trust_model.committer=提交者 -settings.trust_model.committer.long=提交者: 信任与提交者相符的签名(此特性类似 GitHub,这会强制采用 Forgejo 作为提交者和签名者) +settings.trust_model.committer.long=提交者:信任与提交者相符的签名(此特性类似 GitHub,这会强制采用 Forgejo 作为提交者和签名者) settings.trust_model.committer.desc=有效签名只有和提交者相匹配才会被标记为“受信任”,否则它们将被标记为“不匹配”。这强制 Forgejo 成为签名提交的提交者,而实际提交者被加上 Co-authored-by:和 Co-committed-by:的标记。 默认的 Forgejo 密钥必须匹配数据库中的一名用户。 settings.trust_model.collaboratorcommitter=协作者+提交者 settings.trust_model.collaboratorcommitter.long=协作者+提交者:信任协作者同时是提交者的签名 @@ -2500,9 +2500,9 @@ settings.protect_branch_name_pattern=受保护的分支名称正则 settings.protect_branch_name_pattern_desc=受保护的分支名称正则。语法请参阅文档 。如:main, release/** settings.protect_patterns=规则 settings.protect_protected_file_patterns=受保护的文件模式(使用半角分号“;”分隔) -settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用半角分号(“;”)分隔多个模式。 见%[2]s文档了解模式语法。例如: .drone.yml, /docs/**/*.txt。 +settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用半角分号(“;”)分隔多个模式。 见%[2]s文档了解模式语法。例如:.drone.yml, /docs/**/*.txt。 settings.protect_unprotected_file_patterns=不受保护的文件模式(使用半角分号“;”分隔) -settings.protect_unprotected_file_patterns_desc=在用户有写权限的情况下允许绕过限制,直接修改设为不保护的文件。如有多个匹配模式,则可用半角分号(“;”)分隔开。见 %[2]s 的文档以了解匹配模式的格式。例子: .drone.yml/docs/**/*.txt。 +settings.protect_unprotected_file_patterns_desc=在用户有写权限的情况下允许绕过限制,直接修改设为不保护的文件。如有多个匹配模式,则可用半角分号(“;”)分隔开。见 %[2]s 的文档以了解匹配模式的格式。例子:.drone.yml/docs/**/*.txt。 settings.add_protected_branch=启用保护 settings.delete_protected_branch=禁用保护 settings.update_protect_branch_success=分支保护规则 %s 更新成功。 @@ -3081,11 +3081,11 @@ dashboard.task.process=任务:%[1]s dashboard.task.cancelled=任务:%[1]s 已取消:%[3]s dashboard.task.error=任务中的错误:%[1]s:%[3]s dashboard.task.finished=任务:%[2]s 启动的 %[1]s 已完成 -dashboard.task.unknown=未知任务: %[1]s +dashboard.task.unknown=未知任务:%[1]s dashboard.cron.started=已开始计划任务:%[1]s dashboard.cron.process=计划任务:%[1]s dashboard.cron.cancelled=定时任务:%[1]s 已取消:%[3]s -dashboard.cron.error=任务中的错误: %s:%[3]s +dashboard.cron.error=任务中的错误:%s:%[3]s dashboard.cron.finished=任务:%[1]s 已经完成 dashboard.delete_inactive_accounts=删除所有未激活的帐户 dashboard.delete_inactive_accounts.started=删除所有未激活的账户任务已启动。 @@ -3219,7 +3219,7 @@ emails.filter_sort.email_reverse=电子邮件(逆序) emails.filter_sort.name=用户名 emails.filter_sort.name_reverse=用户名(倒序) emails.updated=电子邮件已更新 -emails.not_updated=无法更新请求的电子邮件地址: %v +emails.not_updated=无法更新请求的电子邮件地址:%v emails.duplicate_active=此电子邮件地址已被另一个用户激活使用。 emails.change_email_header=更新电子邮件属性 emails.change_email_text=您确定要更新该电子邮件地址吗? @@ -3245,7 +3245,7 @@ repos.lfs_size=LFS 大小 packages.package_manage_panel=软件包管理 packages.total_size=总大小:%s -packages.unreferenced_size=未引用大小: %s +packages.unreferenced_size=未引用大小:%s packages.cleanup=清理过期数据 packages.cleanup.success=清理过期数据成功 packages.owner=所有者 @@ -3342,7 +3342,7 @@ auths.oauth2_group_claim_name=用于提供用户组名称的 Claim 声明名称 auths.oauth2_admin_group=管理员用户组的 Claim 声明值。(可选 - 需要上面的声明名称) auths.oauth2_restricted_group=受限用户组的 Claim 声明值。(可选 - 需要上面的声明名称) auths.oauth2_map_group_to_team=映射声明的组到组织团队。(可选 - 要求在上面填写声明的名字) -auths.oauth2_map_group_to_team_removal=如果用户不属于相应的组,从已同步团队中移除用户 +auths.oauth2_map_group_to_team_removal=如果用户不属于相应的组,则从同步的团队中移除用户。 auths.enable_auto_register=允许自动注册 auths.sspi_auto_create_users=自动创建用户 auths.sspi_auto_create_users_helper=允许 SSPI 认证在用户第一次登录时自动创建新账号 @@ -3359,7 +3359,7 @@ auths.tips.oauth2.general=OAuth2 认证 auths.tips.oauth2.general.tip=当注册新的 OAuth2 身份验证时,回调/重定向 URL 应该是: auths.tip.oauth2_provider=OAuth2 提供程序 auths.tip.bitbucket=`在 %s -auths.tip.nextcloud=使用下面的菜单“设置(Settings) -> 安全(Security) -> OAuth 2.0 client”在您的实例上注册一个新的 OAuth 客户端。 +auths.tip.nextcloud=使用菜单“设置->安全->OAuth 2.0客户端”在您的实例上注册一个新的 OAuth 客户端。 auths.tip.dropbox=在 %s 上创建一个新的应用程序 auths.tip.facebook=`在 %s 注册一个新的应用,并添加产品"Facebook 登录"` auths.tip.github=在 %s 注册一个 OAuth 应用程序 @@ -3383,7 +3383,7 @@ auths.still_in_used=认证源仍在使用。请先解除或者删除使用此认 auths.deletion_success=认证源已经更新。 auths.login_source_exist=认证源 '%s' 已经存在。 auths.login_source_of_type_exist=此类型的认证源已存在。 -auths.unable_to_initialize_openid=无法初始化 OpenID Connect 提供商: %s +auths.unable_to_initialize_openid=无法初始化 OpenID Connect 提供商:%s auths.invalid_openIdConnectAutoDiscoveryURL=无效的 Auto Discovery URL(这必须是一个以 http:// 或 https://开头的有效的 URL) config.server_config=服务器配置 @@ -3544,7 +3544,7 @@ monitor.process.cancel_notices=中止:%s ? monitor.process.children=子进程 monitor.queues=队列 -monitor.queue=队列: %s +monitor.queue=队列:%s monitor.queue.name=名称 monitor.queue.type=类型 monitor.queue.exemplar=数据类型 @@ -3661,7 +3661,7 @@ raw_minutes=分钟 [dropzone] default_message=拖放文件或点击此处上传。 invalid_input_type=您不能上传该类型的文件。 -file_too_big=文件体积({{filesize}} MB)超过了最大允许体积({{maxFilesize}} MB) +file_too_big=文件体积({{filesize}} MB)超过了最大允许体积({{maxFilesize}} MB)。 remove_file=移除文件 [notification] @@ -3692,7 +3692,7 @@ error.probable_bad_default_signature=警告!虽然默认密钥拥有此ID, [units] unit=单元 error.no_unit_allowed_repo=您没有被允许访问此仓库的任何单元。 -error.unit_not_allowed=您没有权限访问此仓库单元 +error.unit_not_allowed=您没有权限访问此仓库单元。 [packages] title=软件包 @@ -3816,7 +3816,7 @@ settings.delete.error=删除软件包失败。 owner.settings.cargo.title=Cargo 注册中心索引 owner.settings.cargo.initialize=初始化索引 owner.settings.cargo.initialize.description=使用 Cargo 注册中心时需要一个特殊索引的 Git 仓库。使用此选项将(重新)创建仓库并自动配置它。 -owner.settings.cargo.initialize.error=初始化Cargo索引失败: %v +owner.settings.cargo.initialize.error=初始化Cargo索引失败:%v owner.settings.cargo.initialize.success=Cargo索引已经成功创建。 owner.settings.cargo.rebuild=重建索引 owner.settings.cargo.rebuild.description=如果索引与存储的 Cargo 包不同步,重建可能会有用。 @@ -3875,11 +3875,11 @@ alt.repository.multiple_groups = 此软件包在多个组中可用。 [secrets] secrets=密钥 -description=Secrets 将被传给特定的 Actions,其它情况将不能读取 +description=机密将被传给特定的 Action,其它情况将不能被读取。 none=还没有密钥。 creation=添加密钥 creation.name_placeholder=不区分大小写,只能包含英文字母、数字或下划线,不能以 GITEA_ 或 GITHUB_ 开头 -creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略 +creation.value_placeholder=输入任何内容。开头和结尾的空格都会被省略。 creation.success=您的密钥 '%s' 添加成功。 creation.failed=添加密钥失败。 deletion=删除密钥 @@ -3914,7 +3914,7 @@ runners.description=组织描述 runners.labels=标签 runners.last_online=上次在线时间 runners.runner_title=运行器 -runners.task_list=最近在此runner上的任务 +runners.task_list=最近在此运行器上的任务 runners.task_list.no_tasks=还没有任务。 runners.task_list.run=执行 runners.task_list.status=状态 @@ -3930,7 +3930,7 @@ runners.delete_runner_success=运行器删除成功 runners.delete_runner_failed=删除运行器失败 runners.delete_runner_header=确认要删除此运行器 runners.delete_runner_notice=如果一个任务正在运行在此运行器上,它将被终止并标记为失败。它可能会打断正在构建的工作流。 -runners.none=无可用的 Runner +runners.none=无可用的运行器 runners.status.unspecified=未知 runners.status.idle=空闲 runners.status.active=激活 @@ -3943,8 +3943,8 @@ runs.all_workflows=所有工作流 runs.commit=提交 runs.scheduled=已计划的 runs.pushed_by=推送者 -runs.invalid_workflow_helper=工作流配置文件无效。请检查您的配置文件: %s -runs.no_matching_online_runner_helper=没有匹配标签的在线 runner: %s +runs.invalid_workflow_helper=工作流配置文件无效。请检查您的配置文件:%s +runs.no_matching_online_runner_helper=没有匹配标签的在线运行器:%s runs.actor=操作者 runs.status=状态 runs.actors_no_select=所有操作者 @@ -3970,7 +3970,7 @@ variables.creation=添加变量 variables.none=目前还没有变量。 variables.deletion=删除变量 variables.deletion.description=删除变量是永久性的,无法撤消。继续吗? -variables.description=变量将被传给特定的 Actions,其它情况将不能读取 +variables.description=变量将被传给特定的 Action,其它情况将不能被读取。 variables.id_not_exist=ID为 %d 的变量不存在。 variables.edit=编辑变量 variables.deletion.failed=删除变量失败。 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index ea8c1bc2b1..38879b79c3 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -207,6 +207,8 @@ table_modal.header = 新增表格 buttons.indent.tooltip = 使項目縮排一層 buttons.unindent.tooltip = 使項目取消縮排一層 +link_modal.header = 新增連結 + [filter] string.asc=A - Z string.desc=Z - A diff --git a/options/locale_next/locale_fi-FI.json b/options/locale_next/locale_fi-FI.json index 51320bfa8c..c42af6e152 100644 --- a/options/locale_next/locale_fi-FI.json +++ b/options/locale_next/locale_fi-FI.json @@ -1,12 +1,14 @@ { "repo.pulls.merged_title_desc": "yhdistetty %[1]d committia lähteestä %[2]s kohteeseen %[3]s %[4]s", "repo.pulls.title_desc": "haluaa yhdistää %[1]d committia lähteestä %[2]s kohteeseen %[3]s", - "search.milestone_kind": "Etsi merkkipaaluja...", + "search.milestone_kind": "Etsi merkkipaaluja…", "home.welcome.no_activity": "Ei toimintaa", "incorrect_root_url": "Tämä Forgejo-instanssi on määritetty toimimaan osoitteessa \"%s\". Tarkastelet tällä hetkellä Forgejoa eri URL-osoitteen kautta, mikä saattaa aiheuttaa sovelluksen osien toimimattomuutta. Virallinen URL-osoite on Forgejo-ylläpitäjien hallinnoima ROOT_URL-asetus app.ini -tiedostossa.", "themes.names.forgejo-auto": "Forgejo (käyttöjärjestelmän määrittelemä teema)", "home.welcome.activity_hint": "Syötteelläsi ei ole vielä mitään. Toimintasi ja toiminta repositorioissa joita seuraat ilmaantuu tälle sivulle.", "home.explore_repos": "Tutki repositorioita", "home.explore_users": "Tutki käyttäjiä", - "home.explore_orgs": "Tutki organisaatioita" + "home.explore_orgs": "Tutki organisaatioita", + "themes.names.forgejo-light": "Forgejo, vaalea", + "themes.names.forgejo-dark": "Forgejo, tumma" } diff --git a/options/locale_next/locale_pt-BR.json b/options/locale_next/locale_pt-BR.json index 218669787d..85282b101d 100644 --- a/options/locale_next/locale_pt-BR.json +++ b/options/locale_next/locale_pt-BR.json @@ -18,5 +18,6 @@ "incorrect_root_url": "Esta instância do Forgejo está configurada para o endereço \"%s\". Você está atualmente vendo o Forgejo através de uma URL diferente, o que pode causar erros em algumas partes da aplicação. A URL oficial é controlada pela administração do Forgejo através da configuração ROOT_URL no arquivo app.ini.", "themes.names.forgejo-auto": "Forgejo (usar o tema do sistema)", "themes.names.forgejo-light": "Forgejo claro", - "themes.names.forgejo-dark": "Forgejo escuro" + "themes.names.forgejo-dark": "Forgejo escuro", + "install.invalid_lfs_path": "Não foi possível criar um root LFS no caminho especificado: %[1]s" } diff --git a/options/locale_next/locale_pt-PT.json b/options/locale_next/locale_pt-PT.json index 40748e8e2d..4c24ed5ea6 100644 --- a/options/locale_next/locale_pt-PT.json +++ b/options/locale_next/locale_pt-PT.json @@ -18,5 +18,6 @@ "incorrect_root_url": "Esta instância do Forgejo está configurada para ser servida em “%s”. Atualmente, está a visualizar o Forgejo através de um URL diferente, o que pode causar a quebra de partes da aplicação. O URL official é controlado pelos administradores do Forgejo através da configuração ROOT_URL no ficheiro app.ini.", "themes.names.forgejo-auto": "Forgejo (segue o tema do sistema)", "themes.names.forgejo-light": "Forgejo claro", - "themes.names.forgejo-dark": "Forgejo escuro" + "themes.names.forgejo-dark": "Forgejo escuro", + "install.invalid_lfs_path": "Não foi possível criar a raiz LFS no caminho especificado: %[1]s" } From 97220d1ce9d1738e1eca9c4bfc9be82c1363cfa9 Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Thu, 1 May 2025 15:38:05 +0500 Subject: [PATCH 063/115] [v11.0/forgejo] i18n: update of translations from Codeberg Translate Translation updates that are applicable to v11 strings were picked from this commit: 8958dee86e3af119e079849853b4fb44d5d5b4f4 Changes to strings that are only present in the v12 branch were not picked. Below is a list of co-authors of the ported commit. It may contain co-authors who's changes were not picked due to being v12-only. Co-authored-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Benedikt Straub Co-authored-by: Caesar Schinas Co-authored-by: Codeberg Translate Co-authored-by: Dirk Co-authored-by: Edgarsons Co-authored-by: Fjuro Co-authored-by: Juno Takano Co-authored-by: Kita Ikuyo Co-authored-by: Miguel P.L Co-authored-by: SomeTr Co-authored-by: alperen Co-authored-by: earl-warren Co-authored-by: justbispo Co-authored-by: mengzhuo Co-authored-by: neighborsbear Co-authored-by: otf31 Co-authored-by: pboguslawski Co-authored-by: whytf Co-authored-by: xtex Co-authored-by: yurtpage Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/es/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/ko/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/pl/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/sk/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/cs/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/de/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/es/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/fil/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/fr/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/ko/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/lv/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/nds/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/pl/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/pt_BR/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/pt_PT/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/ru/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/sk/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/tr/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/uk/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/zh_Hans/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/zh_Hant/ Translation: Forgejo/forgejo Translation: Forgejo/forgejo-next --- options/locale/locale_cs-CZ.ini | 2 +- options/locale/locale_es-ES.ini | 48 ++++++++++++++++----------- options/locale/locale_ko-KR.ini | 24 +++++++------- options/locale/locale_pl-PL.ini | 2 +- options/locale/locale_ru-RU.ini | 2 +- options/locale/locale_sk-SK.ini | 3 ++ options/locale/locale_tr-TR.ini | 12 ++++--- options/locale/locale_zh-CN.ini | 2 +- options/locale_next/locale_ko-KR.json | 13 ++++---- options/locale_next/locale_pl-PL.json | 3 +- options/locale_next/locale_sk-SK.json | 14 +++++++- 11 files changed, 76 insertions(+), 49 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index e61c3fa90d..6f8953af04 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -939,7 +939,7 @@ access_token_deletion=Odstranit přístupový token access_token_deletion_cancel_action=Zrušit access_token_deletion_confirm_action=Smazat access_token_deletion_desc=Smazání tokenu zruší přístup k vašemu účtu pro aplikace, které jej používají. Tuto akci nelze vrátit. Pokračovat? -delete_token_success=Token byl odstraněn. Aplikace, které jej používají již nemají přístup k vašemu účtu. +delete_token_success=Token byl odstraněn. Aplikace, které jej používají, již nemají přístup k vašemu účtu. repo_and_org_access=Přístup k repozitářům a organizacím permissions_public_only=Pouze veřejné permissions_access_all=Vše (veřejné, soukromé a omezené) diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 70a0d7e23a..954a792a4f 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -20,7 +20,7 @@ notifications=Notificaciones active_stopwatch=Rastreador de tiempo activo create_new=Crear… user_profile_and_more=Perfil y configuración… -signed_in_as=Identificado como +signed_in_as=Conectado como enable_javascript=Este sitio web requiere JavaScript. toc=Tabla de contenidos licenses=Licencias @@ -41,7 +41,7 @@ webauthn_sign_in=Presione el botón en su clave de seguridad. Si su clave de seg webauthn_press_button=Por favor, presione el botón en su clave de seguridad… webauthn_use_twofa=Utilice un código de doble factor desde su teléfono móvil webauthn_error=No se pudo leer su llave de seguridad. -webauthn_unsupported_browser=Su navegador no soporta actualmente WebAuthn. +webauthn_unsupported_browser=Actualmente su navegador no soporta WebAuthn. webauthn_error_unknown=Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo. webauthn_error_insecure=WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1" webauthn_error_unable_to_process=El servidor no pudo procesar su solicitud. @@ -1088,6 +1088,8 @@ access_token_regeneration = Regenerar token de acceso quota.sizes.git.lfs = Git LFS quota.sizes.assets.attachments.issues = Archivos adjuntos de incidencia +keep_pronouns_private.description = Esto ocultará sus pronombres a los visitantes que no hayan iniciado sesión. + [repo] owner=Propietario owner_helper=Algunas organizaciones pueden no aparecer en el menú desplegable debido a un límite máximo de recuento de repositorios. @@ -1107,8 +1109,8 @@ clone_helper=¿Necesita ayuda para clonar? Visite %s` +issues.commented_at=`comentó %s` issues.delete_comment_confirm=¿Seguro que deseas eliminar este comentario? issues.context.copy_link=Copiar enlace issues.context.quote_reply=Citar respuesta @@ -1641,7 +1643,7 @@ issues.context.delete=Eliminar issues.no_content=No se ha proporcionado una descripción. issues.close=Cerrar incidencia issues.comment_pull_merged_at=commit fusionado %[1]s en %[2]s %[3]s -issues.comment_manually_pull_merged_at=commit manualmente fusionado %[1]s en %[2]s %[3]s +issues.comment_manually_pull_merged_at=commit %[1]s manualmente fusionado en %[2]s %[3]s issues.close_comment_issue=Cerrar con comentario issues.reopen_issue=Reabrir issues.reopen_comment_issue=Reabrir con comentario @@ -1666,7 +1668,7 @@ issues.role.collaborator=Colaborador issues.role.collaborator_helper=Este usuario ha sido invitado a colaborar en el repositorio. issues.role.first_time_contributor=Contribuyente por primera vez issues.role.first_time_contributor_helper=Esta es la primera contribución de este usuario al repositorio. -issues.role.contributor=Colaborador +issues.role.contributor=Contribuidor issues.role.contributor_helper=Este usuario ha realizado commit previamente en este repositorio. issues.re_request_review=Solicitar revisión de nuevo issues.is_stale=Ha habido cambios en este PR desde esta revisión @@ -1757,7 +1759,7 @@ issues.error_modifying_due_date=Fallo al modificar la fecha de vencimiento. issues.error_removing_due_date=Fallo al eliminar la fecha de vencimiento. issues.push_commit_1=añadió %d commit %s issues.push_commits_n=añadió %d commits %s -issues.force_push_codes=`hizo push forzado %[1]s de %[2]s a %[4]s %[6]s` +issues.force_push_codes=`empujó forzosamente %[1]s de %[2]s a %[4]s %[6]s` issues.force_push_compare=Comparar issues.due_date_form=aaaa-mm-dd issues.due_date_form_add=Añadir fecha de vencimiento @@ -1876,7 +1878,7 @@ pulls.title_desc_few=quiere fusionar %[1]d commits de %[2]s en %[2]s en %[3]s %[4]s pulls.change_target_branch_at=`cambió la rama objetivo de %s a %s %s` pulls.tab_conversation=Conversación -pulls.tab_commits=Commits +pulls.tab_commits=Confirmaciones pulls.tab_files=Archivos modificados pulls.reopen_to_merge=Vuelva a abrir este Pull Request para realizar una fusión. pulls.cant_reopen_deleted_branch=Este pull request no se puede reabrir porque la rama fue eliminada. @@ -2106,7 +2108,7 @@ activity.git_stats_author_n=%d autores activity.git_stats_pushed_1=ha hecho push activity.git_stats_pushed_n=han hecho push activity.git_stats_commit_1=%d commit -activity.git_stats_commit_n=%d commits +activity.git_stats_commit_n=%d confirmaciones activity.git_stats_push_to_branch=a %s y activity.git_stats_push_to_all_branches=en todas las ramas. activity.git_stats_on_default_branch=En %s, @@ -2256,7 +2258,7 @@ settings.trust_model.default.desc=Utilice el modelo de confianza de repositorio settings.trust_model.collaborator=Colaborador settings.trust_model.collaborator.long=Colaborador: Confiar en firmas de colaboradores settings.trust_model.collaborator.desc=Las firmas válidas de los colaboradores de este repositorio serán marcadas como "confiables" - (coincidan o no con el committer). De lo contrario, las firmas válidas serán marcadas como "no confiables" si la firma coincide con el committer y "no coincidente" si no lo es. -settings.trust_model.committer=Committer +settings.trust_model.committer=Confirmador settings.trust_model.committer.long=Committer: Firmas de confianza que coinciden con los committers (Esto coincide con GitHub y obligará a Forgejo a firmar los commits a tener a Forgejo como el committer) settings.trust_model.committer.desc=Las firmas válidas sólo se marcarán como "confiables" si coinciden con el committer, de lo contrario se marcarán como "no confiable". Esto obliga a Forgejo a ser el committer en commits firmados con el commit real marcado como Co-autorizado por: y Co-commited por: en el tráiler. La clave de Forgejo por defecto debe coincidir con un usuario en la base de datos. settings.trust_model.collaboratorcommitter=Colaborador+Comitter @@ -2675,7 +2677,7 @@ branch.delete_html=Eliminar rama branch.delete_desc=Eliminar una rama es permanente. Aunque la rama eliminada puede continuar existiendo durante un corto tiempo antes de que sea eliminada, en la mayoría de los casos NO PUEDE deshacerse. ¿Continuar? branch.deletion_success=La rama "%s" ha sido eliminada. branch.deletion_failed=Error al eliminar la rama "%s". -branch.delete_branch_has_new_commits=La rama "%s" no se puede eliminar porque se han añadido nuevos commits después de la fusión. +branch.delete_branch_has_new_commits=La rama "%s" no se puede eliminar porque se han añadido nuevas confirmaciones después de la fusión. branch.create_branch=Crear rama %s branch.create_from=`de "%s"` branch.create_success=La rama "%s" ha sido creada. @@ -3832,6 +3834,8 @@ arch.version.makedepends = Construir dependencias arch.version.checkdepends = Comprobar dependencias npm.dependencies.bundle = Empaquetar dependencias +arch.pacman.helper.gpg = Añade el certificado de confianza para pacman: + [secrets] secrets=Secretos description=Los secretos pasarán a ciertas acciones y no se podrán leer de otro modo. @@ -3878,7 +3882,7 @@ runners.task_list.no_tasks=Todavía no hay tarea. runners.task_list.run=Ejecutar runners.task_list.status=Estado runners.task_list.repository=Repositorio -runners.task_list.commit=Commit +runners.task_list.commit=Confirmación runners.task_list.done_at=Hecho en runners.edit_runner=Editar nodo runners.update_runner=Actualizar cambios @@ -3941,6 +3945,10 @@ runs.workflow = Flujo de trabajo workflow.dispatch.use_from = Usar el flujo de trabajo de workflow.dispatch.run = Correr flujo de trabajo +runs.no_workflows = Aún no hay flujos de trabajo. +workflow.dispatch.success = La ejecución del flujo de trabajo se ha solicitado correctamente. +workflow.dispatch.invalid_input_type = Tipo de entrada inválida "%s". + [projects] type-1.display_name=Proyecto individual type-2.display_name=Proyecto de repositorio diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 5e31ae4f19..fe8ba8e13a 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -128,7 +128,7 @@ copy_success = 복사되었습니다! copy_error = 복사 실패 copy_type_unsupported = 이 파일 형식은 복사할 수 없습니다 error = 오류 -error404 = 도달하려는 페이지가 존재하지 않거나 볼 수 있는 권한이 없습니다. +error404 = 도달하려는 페이지가 존재하지 않거나 , 제거 되었거나 또는 볼 권한이 없습니다. go_back = 돌아가기 invalid_data = 유효하지 않는 데이터: %v unknown = 알 수 없음 @@ -176,10 +176,10 @@ footer.software = 이 소프트웨어에 대하여 [heatmap] number_of_contributions_in_the_last_12_months = 지난 12달간 %s 명의 기여자 -contributions_zero = 기여 없음 -contributions_format = {year}년 {month} {day}일에 {contributions} -contributions_one = 기여 -contributions_few = 기여 +contributions_zero = 기여자 없음 +contributions_format = {year}년 {month}월 {day}일의 기여자 {contributions} +contributions_one = 기여자 +contributions_few = 기여자 less = 적은 more = 많은 @@ -376,7 +376,7 @@ allow_password_change=사용자에게 비밀번호 변경을 요청 (권장됨) reset_password_mail_sent_prompt=확인 메일이 %s로 전송되었습니다. 받은 편지함으로 도착한 메일을 %s 안에 확인해서 비밀번호 찾기 절차를 완료하십시오. active_your_account=계정 활성화 account_activated=계정이 활성화 되었습니다 -prohibit_login = +prohibit_login = resent_limit_prompt=활성화를 위한 이메일을 이미 전송했습니다. 3분 내로 이메일을 받지 못한 경우 재시도해주세요. has_unconfirmed_mail=안녕하세요 %s, 이메일 주소(%s)가 확인되지 않았습니다. 확인 메일을 받으시지 못하겼거나 새로운 확인 메일이 필요하다면, 아래 버튼을 클릭해 재발송하실 수 있습니다. resend_mail=여기를 눌러 확인 메일 재전송 @@ -1896,20 +1896,20 @@ branch_kind = 브랜치 검색... keyword_search_unavailable = 지금은 키워드로 검색이 지원되지 않습니다. 사이트 관리자에게 문의하십시오. commit_kind = 커밋 검색... no_results = 일치하는 결과를 찾을 수 없습니다. -search = 검색... +search = 검색… type_tooltip = 검색 타입 fuzzy_tooltip = 검색어와 밀접하게 일치하는 결과도 포함 -repo_kind = 저장소 검색... -user_kind = 사용자 검색... -org_kind = 조직 검색... +repo_kind = 저장소 검색… +user_kind = 사용자 검색… +org_kind = 조직 검색… team_kind = 팀 검색... code_kind = 코드 검색... code_search_unavailable = 코드 검색은 현재 허용되지 않았습니다. 사이트 관리자와 연락하세요. package_kind = 패키지 검색... project_kind = 프로젝트 검색... exact_tooltip = 검색어와 정확하게 일치하는 결과만 포함 -issue_kind = 이슈 검색... -pull_kind = 풀 검색... +issue_kind = 이슈 검색… +pull_kind = 풀 검색… milestone_kind = 마일스톤 검색... fuzzy = 모호함 union = 통합 검색 diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 8aaa680009..86a333a886 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -3616,7 +3616,7 @@ pin=Przypnij powiadomienie mark_as_read=Oznacz jako przeczytane mark_as_unread=Oznacz jak nieprzeczytane mark_all_as_read=Oznacz wszystkie jako przeczytane -subscriptions = Subskrybcje +subscriptions = Subskrypcje no_subscriptions = Brak subskrypcji watching = Obserwowane diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 38d1d885cc..41e2cc39ed 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1694,7 +1694,7 @@ issues.num_participants_few=%d участников issues.attachment.open_tab=`Нажмите, чтобы посмотреть «%s» в новой вкладке` issues.attachment.download=`Нажмите, чтобы скачать «%s»` issues.subscribe=Подписаться -issues.unsubscribe=Отказаться от подписки +issues.unsubscribe=Отписаться issues.unpin_issue=Открепить задачу issues.max_pinned=Нельзя закрепить больше задач issues.pin_comment=закрепил(а) эту задачу %s diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index a90ddd513b..ff8f57d7cb 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -145,6 +145,9 @@ issues = Problémy filter.is_archived = Archivované filter.private = Súkromný +toggle_menu = Prepni menu +more_items = Viac vecí + [aria] navbar=Navigačná lišta footer=Päta diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 2743f63a41..60bca95b96 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -167,6 +167,8 @@ new_migrate.title = Yeni geçiş new_migrate.link = Yeni geçiş copy_path = Dizini kopyala +confirm_delete_artifact = "%s" adlı öğeyi silmek istediğinizden emin misiniz? + [aria] navbar=Gezinti çubuğu footer=Alt Bilgi @@ -3770,13 +3772,13 @@ submodule=Alt modül [search] project_kind = Projeleri ara... -org_kind = Organizasyonları ara... -team_kind = Takımları ara... -search = Ara... -code_kind = Kodları ara... +org_kind = Organizasyonları ara… +team_kind = Takımları ara… +search = Ara… +code_kind = Kod ara… type_tooltip = Arama türü repo_kind = Depoları ara... -user_kind = Kullanıcıları ara... +user_kind = Kullanıcıları ara… milestone_kind = Kilometre taşlarını ara... branch_kind = Dalları ara... package_kind = Paketleri ara... diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 2b0c767f77..8485e6db25 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -3359,7 +3359,7 @@ auths.tips.oauth2.general=OAuth2 认证 auths.tips.oauth2.general.tip=当注册新的 OAuth2 身份验证时,回调/重定向 URL 应该是: auths.tip.oauth2_provider=OAuth2 提供程序 auths.tip.bitbucket=`在 %s -auths.tip.nextcloud=使用菜单“设置->安全->OAuth 2.0客户端”在您的实例上注册一个新的 OAuth 客户端。 +auths.tip.nextcloud=使用菜单“设置->安全->OAuth 2.0客户端”在您的实例上注册一个新的 OAuth 客户端 auths.tip.dropbox=在 %s 上创建一个新的应用程序 auths.tip.facebook=`在 %s 注册一个新的应用,并添加产品"Facebook 登录"` auths.tip.github=在 %s 注册一个 OAuth 应用程序 diff --git a/options/locale_next/locale_ko-KR.json b/options/locale_next/locale_ko-KR.json index 2acaca6084..ac61066356 100644 --- a/options/locale_next/locale_ko-KR.json +++ b/options/locale_next/locale_ko-KR.json @@ -1,8 +1,9 @@ { - "repo.pulls.merged_title_desc": { - "other": "님이 %[2]s 에서 %[3]s 로 %[1]d 커밋을 %[4]s 병합함" - }, - "repo.pulls.title_desc": { - "other": "%[2]s 에서 %[3]s 로 %[1]d개의 커밋들을 병합하려함" - } + "repo.pulls.merged_title_desc": { + "other": "님이 %[2]s 에서 %[3]s 로 %[1]d 커밋을 %[4]s 병합함" + }, + "repo.pulls.title_desc": { + "other": "%[2]s 에서 %[3]s 로 %[1]d개의 커밋들을 병합하려함" + }, + "home.welcome.no_activity": "활동 없음" } diff --git a/options/locale_next/locale_pl-PL.json b/options/locale_next/locale_pl-PL.json index a5870a5539..8f9eea2302 100644 --- a/options/locale_next/locale_pl-PL.json +++ b/options/locale_next/locale_pl-PL.json @@ -1,5 +1,6 @@ { "repo.pulls.merged_title_desc": "scala %[1]d commity/ów z %[2]s do %[3]s %[4]s", "repo.pulls.title_desc": "chce scalić %[1]d commity/ów z %[2]s do %[3]s", - "search.milestone_kind": "Wyszukaj kamienie milowe..." + "search.milestone_kind": "Wyszukaj kamienie milowe...", + "incorrect_root_url": "Ta instancja Forgejo jest skonfigurowana do korzystania z \"%s\". Obecnie oglądasz Forgejo za pomocą innego URL, co może powodować błędne działanie tej aplikacji. URL kanoniczny jest kontrolowany przez administratorów Forgejo za pomocą ROOT_URL w app.ini." } diff --git a/options/locale_next/locale_sk-SK.json b/options/locale_next/locale_sk-SK.json index 0967ef424b..953a7511db 100644 --- a/options/locale_next/locale_sk-SK.json +++ b/options/locale_next/locale_sk-SK.json @@ -1 +1,13 @@ -{} +{ + "home.welcome.no_activity": "Žiadna aktivita", + "home.welcome.activity_hint": "Vo vašom kanáli zatiaľ nič nie je. Tu sa budú zobrazovať vaše akcie a aktivity z repozitárov, ktoré sledujete.", + "home.explore_repos": "Preskúmajte repozitáre", + "home.explore_users": "Preskúmajte používateľov", + "home.explore_orgs": "Preskúmajte organizácie", + "search.milestone_kind": "Hľadať v míľnikoch…", + "incorrect_root_url": "Táto inštancia Forgejo je nakonfigurovaná tak, aby bola obsluhovaná na „%s“. Momentálne zobrazujete Forgejo prostredníctvom inej adresy URL, čo môže spôsobiť poruchu niektorých častí aplikácie. Kanonickú adresu URL kontrolujú správcovia aplikácie Forgejo prostredníctvom nastavenia ROOT_URL v súbore app.ini.", + "themes.names.forgejo-auto": "Forgejo (sleduj systémovú tému)", + "themes.names.forgejo-light": "Forgejo svetlé", + "themes.names.forgejo-dark": "Forgejo tmavé", + "install.invalid_lfs_path": "Nie je možné vytvoriť koreňový systém LFS na zadanej ceste: %[1]s" +} From 1d15e243e4ad89cb9084c79a5a55a2fb6ebfc85a Mon Sep 17 00:00:00 2001 From: 0ko <0ko@noreply.codeberg.org> Date: Thu, 1 May 2025 15:39:19 +0500 Subject: [PATCH 064/115] [v11.0/forgejo] i18n: update of translations from Codeberg Translate Translation updates that are applicable to v11 strings were picked from this commit: 4a51a1f36080464ae52cc12e2086db05456f6ac6 Changes to strings that are only present in the v12 branch were not picked. Below is a list of co-authors of the ported commit. It may contain co-authors who's changes were not picked due to being v12-only. Co-authored-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Benedikt Straub Co-authored-by: Codeberg Translate Co-authored-by: Dirk Co-authored-by: Edgarsons Co-authored-by: Fjuro Co-authored-by: Gusted Co-authored-by: Miguel P.L Co-authored-by: Panagiotis \"Ivory\" Vasilopoulos Co-authored-by: SomeTr Co-authored-by: Yushu Co-authored-by: aleksi Co-authored-by: artnay Co-authored-by: docudoc Co-authored-by: earl-warren Co-authored-by: hugoalh Co-authored-by: mimsee Co-authored-by: pixelcode Co-authored-by: tacaly Co-authored-by: xtex Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/cs/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/de/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/fi/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/nds/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/uk/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/zh_Hans/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo-next/zh_Hant/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/cs/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/da/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/de/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/el/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/es/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/fi/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/fr/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/lv/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/nl/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/ru/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/sv/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/uk/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/zh_Hans/ Translate-URL: https://translate.codeberg.org/projects/forgejo/forgejo/zh_Hant/ Translation: Forgejo/forgejo Translation: Forgejo/forgejo-next --- options/locale/locale_de-DE.ini | 82 +++++++++++++-------------- options/locale/locale_el-GR.ini | 4 ++ options/locale/locale_es-ES.ini | 6 +- options/locale/locale_fi-FI.ini | 46 ++++++++------- options/locale/locale_lv-LV.ini | 20 +++---- options/locale/locale_sv-SE.ini | 8 +-- options/locale/locale_uk-UA.ini | 12 ++-- options/locale/locale_zh-CN.ini | 6 +- options/locale/locale_zh-TW.ini | 10 ++-- options/locale_next/locale_fi-FI.json | 3 +- options/locale_next/locale_zh-TW.json | 2 +- 11 files changed, 106 insertions(+), 93 deletions(-) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index c4801a2fef..0bc8fb7739 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -229,7 +229,7 @@ app_desc=Ein einfacher, selbst gehosteter Git-Service install=Einfach zu installieren install_desc=Starte einfach die Anwendung für deine Plattform oder nutze Docker. Es existieren auch paketierte Versionen. platform=Plattformübergreifend -platform_desc=Forgejo läuft auf freien Betriebssystemen wie Linux und FreeBSD, sowie auf verschiedenen CPU-Architekturen. Wähle das System, das du magst! +platform_desc=Forgejo läuft auf freien Betriebssystemen wie Linux und FreeBSD sowie auf verschiedenen CPU-Architekturen. Wähle das System, das du magst! lightweight=Leichtgewichtig lightweight_desc=Forgejo hat minimale Systemanforderungen und kann selbst auf einem günstigen und stromsparenden Raspberry Pi betrieben werden! license=Quelloffen @@ -339,7 +339,7 @@ default_enable_timetracking=Zeiterfassung standardmäßig aktivieren default_enable_timetracking.description=Zeiterfassung standardmäßig für neue Repositorys aktivieren. no_reply_address=Versteckte E-Mail-Domain no_reply_address_helper=Domain-Name für Benutzer mit einer versteckten Emailadresse. Zum Beispiel wird der Benutzername „Joe“ in Git als „joe@noreply.example.org“ protokolliert, wenn die versteckte E-Mail-Domain „noreply.example.org“ festgelegt ist. -password_algorithm=Passwort Hashing Algorithmus +password_algorithm=Passwort-Hashing-Algorithmus invalid_password_algorithm=Ungültiger Passwort-Hash-Algorithmus password_algorithm_helper=Lege einen Passwort-Hashing-Algorithmus fest. Algorithmen haben unterschiedliche Anforderungen und Stärken. Der argon2-Algorithmus ist ziemlich sicher, aber er verbraucht viel Speicher und kann für kleine Systeme ungeeignet sein. enable_update_checker=Aktualisierungsprüfung aktivieren @@ -347,11 +347,11 @@ env_config_keys=Umgebungskonfiguration env_config_keys_prompt=Die folgenden Umgebungsvariablen werden auch auf Ihre Konfigurationsdatei angewendet: allow_dots_in_usernames = Erlaubt Benutzern die Verwendung von Punkten in ihren Benutzernamen. Hat keine Auswirkungen auf bestehende Konten. enable_update_checker_helper_forgejo = Prüft regelmäßig auf neue Forgejo-Versionen, indem ein DNS-TXT-Eintrag unter release.forgejo.org überprüft wird. -smtp_from_invalid = Die „Sende E-Mail Als“-Adresse ist ungültig +smtp_from_invalid = Die „Sende E-Mail als“-Adresse ist ungültig config_location_hint = Diese Konfigurationsoptionen werden gespeichert in: allow_only_external_registration = Registrierung nur mittels externer Dienste zulassen app_slogan = Instanz-Slogan -app_slogan_helper = Instanz-Slogan hier eingeben. Leer lassen zum deaktivieren. +app_slogan_helper = Instanz-Slogan hier eingeben. Leer lassen zum Deaktivieren. [home] uname_holder=Benutzername oder E-Mail-Adresse @@ -418,7 +418,7 @@ forgot_password_title=Passwort vergessen forgot_password=Passwort vergessen? sign_up_now=Noch kein Konto? Jetzt registrieren. sign_up_successful=Konto wurde erfolgreich erstellt. Willkommen! -confirmation_mail_sent_prompt=Eine neue Bestätigungs-E-Mail wurde an %s gesendet. Um den Registrierungsprozess abzuschließen, überprüf bitte deinen Posteingang und folg dem angegebenen Link innerhalb von: %s. Falls die E-Mail inkorrekt sein sollte, kannst du dich einloggen und anfragen, eine weitere Bestätigungs-E-Mail an eine andere Adresse zu senden. +confirmation_mail_sent_prompt=Eine neue Bestätigungs-E-Mail wurde an %s gesendet. Um den Registrierungsprozess abzuschließen, überprüfe bitte deinen Posteingang und folge dem angegebenen Link innerhalb von: %s. Falls die E-Mail inkorrekt sein sollte, kannst du dich einloggen und anfragen, eine weitere Bestätigungs-E-Mail an eine andere Adresse zu senden. must_change_password=Aktualisiere dein Passwort allow_password_change=Verlange vom Benutzer das Passwort zu ändern (empfohlen) reset_password_mail_sent_prompt=Eine Bestätigungs-E-Mail wurde an %s gesendet. Um den Kontowiederherstellungsprozess abzuschließen, überprüfe bitte deinen Posteingang und folge dem angegebenen Link innerhalb von %s. @@ -451,7 +451,7 @@ oauth_signup_tab=Neues Konto registrieren oauth_signup_title=Neues Konto fertigstellen oauth_signup_submit=Konto vervollständigen oauth_signin_tab=Mit einem existierenden Konto verbinden -oauth_signin_title=Anmelden um verbundenes Konto zu autorisieren +oauth_signin_title=Anmelden, um verbundenes Konto zu autorisieren oauth_signin_submit=Konto verbinden oauth.signin.error=Beim Verarbeiten der Autorisierungsanfrage ist ein Fehler aufgetreten. Wenn dieser Fehler weiterhin besteht, wende dich bitte an deinen Administrator. oauth.signin.error.access_denied=Die Autorisierungsanfrage wurde abgelehnt. @@ -562,7 +562,7 @@ password_change.subject = Dein Passwort wurde geändert password_change.text_1 = Das Passwort für deinen Account wurde soeben geändert. primary_mail_change.subject = Deine primäre E-Mail-Adresse wurde geändert totp_disabled.subject = TOTP wurde deaktiviert -totp_disabled.text_1 = TOTP (Time-based one-time password [Zeitbasiertes Einmalpasswort]) wurde auf deinem Account soeben deaktiviert. +totp_disabled.text_1 = TOTP (Time-based one-time password [zeitbasiertes Einmalpasswort]) wurde auf deinem Account soeben deaktiviert. totp_disabled.no_2fa = Es sind keine anderen 2FA-Methoden mehr konfiguriert, was bedeutet, dass es nicht mehr nötig ist, sich in deinen Account mit 2FA einzuloggen. removed_security_key.subject = Ein Sicherheitsschlüssel wurde entfernt removed_security_key.no_2fa = Es sind keine anderen 2FA-Methoden mehr konfiguriert, was bedeutet, dass es nicht mehr nötig ist, sich in deinen Account mit 2FA einzuloggen. @@ -572,7 +572,7 @@ reset_password.text_1 = Das Passwort für deinen Account wurde soeben geändert. primary_mail_change.text_1 = Die primäre E-Mail-Adresse deines Account wurde soeben zu %[1]s geändert. Das bedeutet, dass diese E-Mail-Adresse keine E-Mail-Benachrichtigungen für deinen Account erhalten wird. account_security_caution.text_2 = Wenn du das nicht warst, wurde dein Account kompromittiert. Bitte kontaktiere die Admins dieser Webseite. totp_enrolled.subject = Du hast TOTP als 2FA-Methode aktiviert -totp_enrolled.text_1.has_webauthn = Du hast gerade eben TOTP für deinen Account aktiviert. Das bedeutet, dass du in Zukunft für alle Logins in deinen Account TOTP als 2FA-Methode benutzen könntest, oder einen deiner Sicherheitsschlüssel. +totp_enrolled.text_1.has_webauthn = Du hast gerade eben TOTP für deinen Account aktiviert. Das bedeutet, dass du in Zukunft für alle Logins in deinen Account TOTP als 2FA-Methode oder einen deiner Sicherheitsschlüssel benutzen könntest. totp_enrolled.text_1.no_webauthn = Du hast gerade eben TOTP für deinen Account aktiviert. Das bedeutet, dass du in Zukunft für alle Logins in deinen Account TOTP als 2FA-Methode benutzen musst. [modal] @@ -661,8 +661,8 @@ organization_leave_success=Du hast die Organisation %s erfolgreich verlassen. invalid_ssh_key=Dein SSH-Key kann nicht überprüft werden: %s invalid_gpg_key=Dein GPG-Key kann nicht überprüft werden: %s invalid_ssh_principal=Ungültige Identität: %s -must_use_public_key=Der von dir bereitgestellte Key ist ein privater Key. Bitte lade deinen privaten Key nirgendwo hoch. Verwende stattdessen deinen öffentlichen Key. -unable_verify_ssh_key=Der SSH-Key kann nicht verifiziert werden, überprüfe ihn auf Fehler. +must_use_public_key=Der von dir bereitgestellte Schlüssel ist ein privater. Bitte lade deinen privaten Schlüssel nirgendwo hoch, sondern verwende stattdessen deinen öffentlichen. +unable_verify_ssh_key=Der SSH-Schlüssel kann nicht verifiziert werden, überprüfe ihn auf Fehler. auth_failed=Authentifizierung fehlgeschlagen: %v still_own_repo=Dein Konto besitzt ein oder mehrere Repositorys. Diese müssen erst gelöscht oder übertragen werden. @@ -700,7 +700,7 @@ watched=Beobachtete Repositorys code=Quelltext projects=Projekte overview=Übersicht -following_few=%d Folge ich +following_few=%d folge ich follow=Folgen unfollow=Nicht mehr folgen user_bio=Biografie @@ -722,7 +722,7 @@ follow_blocked_user = Du kannst diesen Benutzer nicht folgen, weil du ihn blocki block_user.detail_3 = Ihr werdet nicht mehr in der Lage sein, euch gegenseitig als Repository-Mitarbeiter hinzuzufügen. unblock = Nicht mehr blockieren followers_one = %d Follower -following_one = %d Folge ich +following_one = %d folge ich followers.title.few = Follower following.title.one = Folgt following.title.few = Folgt @@ -774,7 +774,7 @@ cancel=Abbrechen language=Sprache ui=Theme hidden_comment_types=Ausgeblendete Kommentartypen -hidden_comment_types_description=Die hier markierten Kommentartypen werden nicht innerhalb der Issue-Seiten angezeigt. Die Markierung von „Label“ zum Beispiel entfernt alle Kommentare der Form „ hat