mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-13 22:40:24 +00:00
Fixes #9629. New pull mirrors have credentials stored encrypted in the database, the same as push mirrors, rather than in the repository's `config` file. `git fetch` on the pull mirror is updated to use the credential store. Pull mirrors will have their credentials migrated to the encrypted storage in the database as they're synced or otherwise accessed via the web UI. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). 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 for Go changes - 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 ran... - [x] `make pr-go` before pushing ### 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 - [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11909 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org> Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net> Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
579 lines
20 KiB
Go
579 lines
20 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"forgejo.org/models/auth"
|
|
"forgejo.org/models/db"
|
|
repo_model "forgejo.org/models/repo"
|
|
"forgejo.org/models/unittest"
|
|
user_model "forgejo.org/models/user"
|
|
"forgejo.org/modules/git"
|
|
"forgejo.org/modules/gitrepo"
|
|
"forgejo.org/modules/migration"
|
|
"forgejo.org/modules/optional"
|
|
"forgejo.org/modules/process"
|
|
"forgejo.org/modules/setting"
|
|
"forgejo.org/modules/structs"
|
|
app_context "forgejo.org/services/context"
|
|
"forgejo.org/services/forms"
|
|
"forgejo.org/services/migrations"
|
|
mirror_service "forgejo.org/services/mirror"
|
|
release_service "forgejo.org/services/release"
|
|
repo_service "forgejo.org/services/repository"
|
|
files_service "forgejo.org/services/repository/files"
|
|
"forgejo.org/tests"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMirrorPull(t *testing.T) {
|
|
t.Run("Basic", func(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
repoPath := repo_model.RepoPath(user.Name, repo.Name)
|
|
|
|
opts := migration.MigrateOptions{
|
|
RepoName: "test_mirror",
|
|
Description: "Test mirror",
|
|
Private: false,
|
|
Mirror: true,
|
|
CloneAddr: repoPath,
|
|
Wiki: true,
|
|
Releases: false,
|
|
}
|
|
|
|
mirrorRepo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, user, repo_service.CreateRepoOptions{
|
|
Name: opts.RepoName,
|
|
Description: opts.Description,
|
|
IsPrivate: opts.Private,
|
|
IsMirror: opts.Mirror,
|
|
Status: repo_model.RepositoryBeingMigrated,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, mirrorRepo.IsMirror, "expected pull-mirror repo to be marked as a mirror immediately after its creation")
|
|
|
|
ctx := t.Context()
|
|
|
|
mirror, err := repo_service.MigrateRepositoryGitData(ctx, user, mirrorRepo, opts, nil)
|
|
require.NoError(t, err)
|
|
|
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo)
|
|
require.NoError(t, err)
|
|
defer gitRepo.Close()
|
|
|
|
findOptions := repo_model.FindReleasesOptions{
|
|
IncludeDrafts: true,
|
|
IncludeTags: true,
|
|
RepoID: mirror.ID,
|
|
}
|
|
initCount, err := db.Count[repo_model.Release](db.DefaultContext, findOptions)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, release_service.CreateRelease(gitRepo, &repo_model.Release{
|
|
RepoID: repo.ID,
|
|
Repo: repo,
|
|
PublisherID: user.ID,
|
|
Publisher: user,
|
|
TagName: "v0.2",
|
|
Target: "master",
|
|
Title: "v0.2 is released",
|
|
Note: "v0.2 is released",
|
|
IsDraft: false,
|
|
IsPrerelease: false,
|
|
IsTag: true,
|
|
}, "", []*release_service.AttachmentChange{}))
|
|
|
|
_, err = repo_model.GetMirrorByRepoID(ctx, mirror.ID)
|
|
require.NoError(t, err)
|
|
|
|
ok := mirror_service.SyncPullMirror(ctx, mirror.ID)
|
|
assert.True(t, ok)
|
|
|
|
count, err := db.Count[repo_model.Release](db.DefaultContext, findOptions)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, initCount+1, count)
|
|
|
|
release, err := repo_model.GetRelease(db.DefaultContext, repo.ID, "v0.2")
|
|
require.NoError(t, err)
|
|
require.NoError(t, release_service.DeleteReleaseByID(ctx, repo, release, user, true))
|
|
|
|
ok = mirror_service.SyncPullMirror(ctx, mirror.ID)
|
|
assert.True(t, ok)
|
|
|
|
count, err = db.Count[repo_model.Release](db.DefaultContext, findOptions)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, initCount, count)
|
|
})
|
|
|
|
// How will we interact with the pull mirror during this test?
|
|
interactionMethod := []struct {
|
|
name string
|
|
useAPI bool
|
|
createPullMirror func(t *testing.T, sourceRepo *repo_model.Repository, authenticate bool) (repoName string)
|
|
verifyMirrorDetails func(t *testing.T, sourceRepo *repo_model.Repository, mirrorName string, authenticate bool)
|
|
triggerPullMirror func(t *testing.T, mirrorName string)
|
|
changePullMirrorCredentials func(t *testing.T, sourceRepo *repo_model.Repository, mirrorName string, authenticate bool)
|
|
changePullMirrorAddress func(t *testing.T, sourceRepo *repo_model.Repository, mirrorName string, authenticate bool)
|
|
}{
|
|
{
|
|
name: "API",
|
|
useAPI: true,
|
|
createPullMirror: createPullMirrorViaAPI,
|
|
triggerPullMirror: triggerPullMirrorViaAPI,
|
|
verifyMirrorDetails: func(t *testing.T, sourceRepo *repo_model.Repository, mirrorName string, authenticate bool) {
|
|
// API provides no visibility into a repo's mirror settings right now
|
|
},
|
|
},
|
|
{
|
|
name: "Web",
|
|
useAPI: false,
|
|
createPullMirror: createPullMirrorViaWeb,
|
|
triggerPullMirror: triggerPullMirrorViaWeb,
|
|
verifyMirrorDetails: verifyPullMirrorViaWeb,
|
|
changePullMirrorCredentials: changePullMirrorCredentialsViaWeb,
|
|
changePullMirrorAddress: changePullMirrorCredentialsViaWeb, // one endpoint, so same as creds
|
|
},
|
|
}
|
|
|
|
mirrorConfiguration := []struct {
|
|
name string
|
|
privateSource bool
|
|
}{
|
|
{
|
|
name: "HTTP Without Auth",
|
|
},
|
|
{
|
|
name: "HTTP With Auth",
|
|
privateSource: true,
|
|
},
|
|
}
|
|
|
|
// Not using MockVariableValue due to need to undo `migrations.Init()`
|
|
prev := setting.Migrations.AllowedDomains
|
|
setting.Migrations.AllowedDomains = "localhost"
|
|
migrations.Init() // reinitialize for changed allowList
|
|
defer func() {
|
|
setting.Migrations.AllowedDomains = prev
|
|
migrations.Init() // reinitialize for changed allowList
|
|
}()
|
|
|
|
onApplicationRun(t, func(t *testing.T, u *url.URL) {
|
|
for _, im := range interactionMethod {
|
|
for _, mc := range mirrorConfiguration {
|
|
t.Run(fmt.Sprintf("%s/%s", im.name, mc.name), func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
|
|
// Create the source repository that will be mirrored.
|
|
sourceRepo, sourceRepoSha, cleanupSource := tests.CreateDeclarativeRepoWithOptions(t, user2,
|
|
tests.DeclarativeRepoOptions{
|
|
IsPrivate: optional.Some(mc.privateSource),
|
|
Files: optional.Some([]*files_service.ChangeRepoFile{
|
|
{
|
|
Operation: "create",
|
|
TreePath: "docs.md",
|
|
ContentReader: strings.NewReader("hello, world"),
|
|
},
|
|
}),
|
|
},
|
|
)
|
|
defer cleanupSource()
|
|
require.NotEmpty(t, sourceRepoSha)
|
|
|
|
// Create pull mirror
|
|
mirror := im.createPullMirror(t, sourceRepo, mc.privateSource)
|
|
verifyPullMirrorContents(t, mirror, sourceRepoSha)
|
|
verifyPullMirrorConfig(t, mirror, sourceRepo, mc.privateSource)
|
|
im.verifyMirrorDetails(t, sourceRepo, mirror, mc.privateSource)
|
|
|
|
// Push a change to the source and refresh the mirror
|
|
sourceRepoSha = changePullMirrorSource(t, sourceRepo, sourceRepoSha)
|
|
im.triggerPullMirror(t, mirror)
|
|
waitForPullMirror(t, mirror, sourceRepoSha)
|
|
|
|
// Test changing the mirror's authentication method (if available)
|
|
if mc.privateSource && im.changePullMirrorCredentials != nil {
|
|
sourceRepoSha = changePullMirrorSource(t, sourceRepo, sourceRepoSha)
|
|
im.changePullMirrorCredentials(t, sourceRepo, mirror, mc.privateSource)
|
|
verifyPullMirrorConfig(t, mirror, sourceRepo, mc.privateSource)
|
|
im.verifyMirrorDetails(t, sourceRepo, mirror, mc.privateSource)
|
|
im.triggerPullMirror(t, mirror)
|
|
waitForPullMirror(t, mirror, sourceRepoSha)
|
|
}
|
|
|
|
// Test changing the mirror's address (if available)
|
|
if im.changePullMirrorAddress != nil {
|
|
sourceRepo = renamePullMirrorSourceRepo(t, sourceRepo)
|
|
sourceRepoSha = changePullMirrorSource(t, sourceRepo, sourceRepoSha)
|
|
im.changePullMirrorAddress(t, sourceRepo, mirror, mc.privateSource)
|
|
verifyPullMirrorConfig(t, mirror, sourceRepo, mc.privateSource)
|
|
im.verifyMirrorDetails(t, sourceRepo, mirror, mc.privateSource)
|
|
im.triggerPullMirror(t, mirror)
|
|
waitForPullMirror(t, mirror, sourceRepoSha)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("migrate from repo config credentials", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
|
|
mirrorRepo, _, cleanupMirror := tests.CreateDeclarativeRepoWithOptions(t, user2,
|
|
tests.DeclarativeRepoOptions{},
|
|
)
|
|
defer cleanupMirror()
|
|
|
|
// Write to the repo a config file that would have plausibly existed before EncryptedRemoteAddress was
|
|
// introduced:
|
|
repoPath := mirrorRepo.RepoPath()
|
|
err := os.WriteFile(path.Join(repoPath, "config"), []byte(`
|
|
[core]
|
|
repositoryformatversion = 0
|
|
filemode = true
|
|
bare = true
|
|
[remote "origin"]
|
|
url = https://user:password@example.com/org/repo.git
|
|
tagOpt = --no-tags
|
|
fetch = +refs/*:refs/*
|
|
mirror = true
|
|
fetch = +refs/tags/*:refs/tags/*
|
|
`), 0o644)
|
|
require.NoError(t, err)
|
|
|
|
// Create a Mirror record without an EncryptedRemoteAddress:
|
|
mirror := &repo_model.Mirror{
|
|
RepoID: mirrorRepo.ID,
|
|
Interval: 8 * time.Hour,
|
|
EnablePrune: true,
|
|
}
|
|
_, err = db.GetEngine(t.Context()).Insert(mirror)
|
|
require.NoError(t, err)
|
|
require.Nil(t, mirror.EncryptedRemoteAddress)
|
|
|
|
remoteURL, err := mirror_service.DecryptOrRecoverRemoteAddress(t.Context(), mirror)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "https://user:password@example.com/org/repo.git", remoteURL.URL.String())
|
|
|
|
// EncryptedRemoteAddress should now be populated from the recovery:
|
|
assert.NotNil(t, mirror.EncryptedRemoteAddress)
|
|
maybeDecryptedURL, err := mirror.DecryptRemoteAddress()
|
|
require.NoError(t, err)
|
|
has, decryptedURL := maybeDecryptedURL.Get()
|
|
require.True(t, has)
|
|
assert.Equal(t, "https://user:password@example.com/org/repo.git", decryptedURL)
|
|
|
|
// SanitizedRemoteAddress can be fetched:
|
|
maybeSanitizedURL, err := mirror.SanitizedRemoteAddress()
|
|
require.NoError(t, err)
|
|
has, sanitizedURL := maybeSanitizedURL.Get()
|
|
require.True(t, has)
|
|
assert.Equal(t, "https://user@example.com/org/repo.git", sanitizedURL)
|
|
|
|
// Database record is updated in the database:
|
|
refetchMirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID})
|
|
assert.Equal(t, mirror.EncryptedRemoteAddress, refetchMirror.EncryptedRemoteAddress)
|
|
|
|
// Config file is rewritten:
|
|
config, err := os.ReadFile(path.Join(repoPath, "config"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, `
|
|
[core]
|
|
repositoryformatversion = 0
|
|
filemode = true
|
|
bare = true
|
|
[remote "origin"]
|
|
url = https://user@example.com/org/repo.git
|
|
tagOpt = --no-tags
|
|
fetch = +refs/*:refs/*
|
|
mirror = true
|
|
fetch = +refs/tags/*:refs/tags/*
|
|
`, string(config))
|
|
})
|
|
}
|
|
|
|
func createPullMirrorViaWeb(t *testing.T, sourceRepo *repo_model.Repository, authenticate bool) string {
|
|
session := loginUser(t, "user2")
|
|
|
|
mirrorName := fmt.Sprintf("pullmirror-%s", sourceRepo.Name)
|
|
form := &forms.MigrateRepoForm{
|
|
CloneAddr: sourceRepo.CloneLink().HTTPS,
|
|
Service: structs.PlainGitService,
|
|
UID: 2,
|
|
RepoName: mirrorName,
|
|
Mirror: true,
|
|
}
|
|
if authenticate {
|
|
form.AuthUsername = "user2"
|
|
form.AuthPassword = getTokenForLoggedInUser(t, session, auth.AccessTokenScopeReadRepository)
|
|
}
|
|
|
|
resp := session.MakeRequest(t,
|
|
NewRequestWithJSON(t, "POST", "/repo/migrate", form),
|
|
http.StatusSeeOther)
|
|
location := resp.Header().Get("Location")
|
|
assert.Equal(t, fmt.Sprintf("/user2/pullmirror-%s", sourceRepo.Name), location)
|
|
|
|
var lastBody string
|
|
if !assert.Eventuallyf(t,
|
|
func() bool {
|
|
resp := session.MakeRequest(t,
|
|
NewRequest(t, "GET", location),
|
|
http.StatusOK)
|
|
body := resp.Body.String()
|
|
lastBody = body
|
|
// Looking for the repo page to be fully populated indicating that the migration is complete:
|
|
// Check that the first commit message is present:
|
|
if !strings.Contains(body, "Initial commit") {
|
|
return false
|
|
}
|
|
// Check that the fork button is present:
|
|
if !strings.Contains(body, fmt.Sprintf("/user2/%s/fork", mirrorName)) {
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
15*time.Second, 1*time.Second,
|
|
"expected migration to complete and repo page to render") {
|
|
t.Logf("last received page body: %s", lastBody)
|
|
}
|
|
|
|
return mirrorName
|
|
}
|
|
|
|
func createPullMirrorViaAPI(t *testing.T, sourceRepo *repo_model.Repository, authenticate bool) string {
|
|
session := loginUser(t, "user2")
|
|
apiToken := getTokenForLoggedInUser(t, session, auth.AccessTokenScopeWriteRepository)
|
|
|
|
mirrorName := fmt.Sprintf("pullmirror-%s", sourceRepo.Name)
|
|
form := &structs.MigrateRepoOptions{
|
|
CloneAddr: sourceRepo.CloneLink().HTTPS,
|
|
Service: "git",
|
|
RepoOwner: "user2",
|
|
RepoName: mirrorName,
|
|
Mirror: true,
|
|
}
|
|
if authenticate {
|
|
form.AuthUsername = "user2"
|
|
form.AuthPassword = getTokenForLoggedInUser(t, session, auth.AccessTokenScopeReadRepository)
|
|
}
|
|
|
|
resp := session.MakeRequest(t,
|
|
NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", form).AddTokenAuth(apiToken),
|
|
http.StatusCreated)
|
|
var repo structs.Repository
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.NotNil(t, repo)
|
|
assert.True(t, repo.Mirror)
|
|
assert.False(t, repo.Empty)
|
|
|
|
return mirrorName
|
|
}
|
|
|
|
func verifyPullMirrorViaWeb(t *testing.T, sourceRepo *repo_model.Repository, mirrorName string, authenticate bool) {
|
|
session := loginUser(t, "user2")
|
|
resp := session.MakeRequest(t,
|
|
NewRequestf(t, "GET", "/user2/%s/settings", mirrorName),
|
|
http.StatusOK)
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
htmlDoc.AssertAttrEqual(t, "#mirror_address", "value", sourceRepo.CloneLink().HTTPS)
|
|
if authenticate {
|
|
htmlDoc.AssertAttrEqual(t, "#mirror_username", "value", "user2")
|
|
htmlDoc.AssertAttrEqual(t, "#mirror_password", "value", "")
|
|
htmlDoc.AssertAttrEqual(t, "#mirror_password", "placeholder", "(Unchanged)")
|
|
} else {
|
|
htmlDoc.AssertAttrEqual(t, "#mirror_username", "value", "")
|
|
htmlDoc.AssertAttrEqual(t, "#mirror_password", "value", "")
|
|
htmlDoc.AssertAttrEqual(t, "#mirror_password", "placeholder", "(Unset)")
|
|
}
|
|
|
|
resp = session.MakeRequest(t,
|
|
NewRequestf(t, "GET", "/user2/%s", mirrorName),
|
|
http.StatusOK)
|
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
htmlDoc.AssertElementPredicate(t, ".fork-flag", func(selection *goquery.Selection) bool {
|
|
text := strings.TrimSpace(selection.Text())
|
|
assert.Contains(t, text, "mirror of")
|
|
assert.Contains(t, text, sourceRepo.CloneLink().HTTPS)
|
|
return true
|
|
})
|
|
}
|
|
|
|
func triggerPullMirrorViaWeb(t *testing.T, mirrorName string) {
|
|
session := loginUser(t, "user2")
|
|
|
|
resp := session.MakeRequest(t,
|
|
NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/%s/settings", mirrorName), map[string]string{"action": "mirror-sync"}),
|
|
http.StatusSeeOther)
|
|
location := resp.Header().Get("Location")
|
|
assert.Equal(t, fmt.Sprintf("/user2/%s/settings", mirrorName), location)
|
|
}
|
|
|
|
func triggerPullMirrorViaAPI(t *testing.T, mirrorName string) {
|
|
session := loginUser(t, "user2")
|
|
apiToken := getTokenForLoggedInUser(t, session, auth.AccessTokenScopeWriteRepository)
|
|
|
|
// Trigger sync...
|
|
session.MakeRequest(t,
|
|
NewRequestf(t, "POST", "/api/v1/repos/user2/%s/mirror-sync", mirrorName).AddTokenAuth(apiToken),
|
|
http.StatusOK)
|
|
}
|
|
|
|
func changePullMirrorCredentialsViaWeb(t *testing.T, sourceRepo *repo_model.Repository, mirrorName string, authenticate bool) {
|
|
session := loginUser(t, "user2")
|
|
|
|
form := map[string]string{
|
|
"action": "mirror",
|
|
"enable_prune": "on",
|
|
"interval": "8h0m0s",
|
|
"mirror_address": sourceRepo.CloneLink().HTTPS,
|
|
}
|
|
if authenticate {
|
|
form["mirror_username"] = "user2"
|
|
form["mirror_password"] = getTokenForLoggedInUser(t, session, auth.AccessTokenScopeReadRepository)
|
|
}
|
|
|
|
resp := session.MakeRequest(t,
|
|
NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/%s/settings", mirrorName), form),
|
|
http.StatusSeeOther)
|
|
location := resp.Header().Get("Location")
|
|
assert.Equal(t, fmt.Sprintf("/user2/%s/settings", mirrorName), location)
|
|
}
|
|
|
|
func verifyPullMirrorContents(t *testing.T, mirrorName, expectedSha string) {
|
|
session := loginUser(t, "user2")
|
|
apiToken := getTokenForLoggedInUser(t, session, auth.AccessTokenScopeReadRepository)
|
|
resp := session.MakeRequest(t,
|
|
NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/%s/commits?sha=main&limit=1", mirrorName)).AddTokenAuth(apiToken),
|
|
http.StatusOK)
|
|
var commits []*structs.Commit
|
|
DecodeJSON(t, resp, &commits)
|
|
require.Len(t, commits, 1)
|
|
assert.Equal(t, expectedSha, commits[0].SHA)
|
|
}
|
|
|
|
func waitForPullMirror(t *testing.T, mirrorName, expectedSha string) {
|
|
session := loginUser(t, "user2")
|
|
apiToken := getTokenForLoggedInUser(t, session, auth.AccessTokenScopeReadRepository)
|
|
|
|
var commits []*structs.Commit
|
|
if !assert.Eventually(t,
|
|
func() bool {
|
|
resp := session.MakeRequest(t,
|
|
NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/%s/commits?sha=main&limit=1", mirrorName)).AddTokenAuth(apiToken),
|
|
http.StatusOK)
|
|
DecodeJSON(t, resp, &commits)
|
|
require.Len(t, commits, 1)
|
|
return commits[0].SHA == expectedSha
|
|
},
|
|
15*time.Second, 1*time.Second) {
|
|
t.Logf("sync was supposed to bring repo to commit %s, but observed commits = %#v", expectedSha, commits)
|
|
}
|
|
}
|
|
|
|
func getGitConfig(t *testing.T, configFile, configPath string) string {
|
|
stdout, stderr, err := process.GetManager().Exec("getGitConfig", "git", "config", "get", "--file", configFile, configPath)
|
|
require.NoError(t, err, "fetch config %s failed: git stderr: %s", configPath, stderr)
|
|
return strings.TrimSpace(stdout)
|
|
}
|
|
|
|
func verifyPullMirrorConfig(t *testing.T, mirrorName string, sourceRepo *repo_model.Repository, authenticate bool) {
|
|
mirrorRepo, err := repo_model.GetRepositoryByOwnerAndName(t.Context(), "user2", mirrorName)
|
|
require.NoError(t, err)
|
|
|
|
repoPath := mirrorRepo.RepoPath()
|
|
configPath := path.Join(repoPath, "config")
|
|
|
|
expectedURL := sourceRepo.CloneLink().HTTPS
|
|
if authenticate {
|
|
expectedURL = strings.Replace(expectedURL, "http://", "http://user2@", 1)
|
|
}
|
|
assert.Equal(t, expectedURL, getGitConfig(t, configPath, "remote.origin.url"))
|
|
assert.Equal(t, "true", getGitConfig(t, configPath, "remote.origin.mirror"))
|
|
assert.Equal(t, "+refs/tags/*:refs/tags/*", getGitConfig(t, configPath, "remote.origin.fetch"))
|
|
}
|
|
|
|
func changePullMirrorSource(t *testing.T, sourceRepo *repo_model.Repository, sourceRepoSha string) string {
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
resp, err := files_service.ChangeRepoFiles(git.DefaultContext, sourceRepo, user2,
|
|
&files_service.ChangeRepoFilesOptions{
|
|
Files: []*files_service.ChangeRepoFile{
|
|
{
|
|
Operation: "update",
|
|
TreePath: "docs.md",
|
|
ContentReader: strings.NewReader(uuid.NewString()),
|
|
},
|
|
},
|
|
Message: "add files",
|
|
OldBranch: "main",
|
|
NewBranch: "main",
|
|
Author: &files_service.IdentityOptions{
|
|
Name: user2.Name,
|
|
Email: user2.Email,
|
|
},
|
|
Committer: &files_service.IdentityOptions{
|
|
Name: user2.Name,
|
|
Email: user2.Email,
|
|
},
|
|
Dates: &files_service.CommitDateOptions{
|
|
Author: time.Now(),
|
|
Committer: time.Now(),
|
|
},
|
|
LastCommitID: sourceRepoSha,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, resp)
|
|
return resp.Commit.SHA
|
|
}
|
|
|
|
func renamePullMirrorSourceRepo(t *testing.T, sourceRepo *repo_model.Repository) *repo_model.Repository {
|
|
session := loginUser(t, "user2")
|
|
apiToken := getTokenForLoggedInUser(t, session, auth.AccessTokenScopeWriteRepository)
|
|
|
|
newName := uuid.NewString()
|
|
session.MakeRequest(t,
|
|
NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/user2/%s", sourceRepo.Name),
|
|
&structs.EditRepoOption{
|
|
Name: &newName,
|
|
}).AddTokenAuth(apiToken),
|
|
http.StatusOK)
|
|
|
|
newRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: sourceRepo.ID})
|
|
assert.Equal(t, newRepo.Name, newName)
|
|
assert.NotEqual(t, newRepo.CloneLink().HTTPS, sourceRepo.CloneLink().HTTPS) // should have changed to new name
|
|
return newRepo
|
|
}
|
|
|
|
func TestPullMirrorRedactCredentials(t *testing.T) {
|
|
defer unittest.OverrideFixtures("tests/integration/fixtures/TestPullMirrorRedactCredentials")()
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user2/repo1001/settings", map[string]string{
|
|
"action": "mirror-sync",
|
|
}), http.StatusSeeOther)
|
|
|
|
flashCookie := session.GetCookie(app_context.CookieNameFlash)
|
|
assert.NotNil(t, flashCookie)
|
|
assert.Equal(t, "info%3DPulling%2Bchanges%2Bfrom%2Bthe%2Bremote%2Bhttps%253A%252F%252Fexample.com%252Fexample%252Fexample.git%2Bat%2Bthe%2Bmoment.", flashCookie.Value)
|
|
}
|