Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add push to remote mirror repository #15157

Merged
merged 73 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from 64 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
4e0e107
Added push mirror model.
KN4CK3R Mar 22, 2021
5797f96
Integrated push mirror into queue.
KN4CK3R Mar 24, 2021
00bf264
Moved methods into own file.
KN4CK3R Mar 25, 2021
000cf56
Added basic implementation.
KN4CK3R Mar 26, 2021
5e701eb
Mirror wiki too.
KN4CK3R Mar 27, 2021
ecbbc67
Removed duplicated method.
KN4CK3R Mar 28, 2021
f6f4dba
Get url for different remotes.
KN4CK3R Mar 28, 2021
2365760
Added migration.
KN4CK3R Mar 31, 2021
aff13d6
Unified remote url access.
KN4CK3R Mar 31, 2021
ff4b4ab
Add/Remove push mirror remotes.
KN4CK3R Mar 31, 2021
382be07
Prevent hangs with missing credentials.
KN4CK3R Mar 31, 2021
bf22cd1
Moved code between files.
KN4CK3R Apr 1, 2021
5604087
Lint
KN4CK3R Apr 1, 2021
84a9bd8
Changed sanitizer interface.
KN4CK3R Apr 3, 2021
ca14354
Added push mirror backend methods.
KN4CK3R Apr 3, 2021
a9b1961
Fix
KN4CK3R Apr 3, 2021
ca2c530
Only update the mirror remote.
KN4CK3R Apr 4, 2021
84d1352
Limit refs on push.
KN4CK3R Apr 5, 2021
806408a
Added UI part.
KN4CK3R Apr 5, 2021
4eed55d
Merge branch 'master' of https://github.com/go-gitea/gitea into featu…
KN4CK3R Apr 5, 2021
79201d7
Added missing table.
KN4CK3R Apr 5, 2021
b78b83e
Delete mirror if repository gets removed.
KN4CK3R Apr 6, 2021
96dab54
Merge branch 'master' of https://github.com/go-gitea/gitea into featu…
KN4CK3R Apr 9, 2021
07ad389
Merge branch 'master' of https://github.com/go-gitea/gitea into featu…
KN4CK3R Apr 13, 2021
bafeba1
Merge branch 'master' of https://github.com/go-gitea/gitea into featu…
KN4CK3R Apr 17, 2021
c0d3c78
Changed signature. Handle object errors.
KN4CK3R Apr 17, 2021
20de69c
Added upload method.
KN4CK3R Apr 17, 2021
92e2573
Added "upload" unit tests.
KN4CK3R Apr 18, 2021
0d9c384
Added transfer adapter unit tests.
KN4CK3R Apr 18, 2021
5746665
Merge branch 'master' of https://github.com/go-gitea/gitea into featu…
KN4CK3R Apr 18, 2021
3bc2300
Send correct headers.
KN4CK3R Apr 18, 2021
c4ff5d2
Added pushing of LFS objects.
KN4CK3R Apr 19, 2021
0012f34
Go 1.14
KN4CK3R Apr 19, 2021
bacae71
Added more logging.
KN4CK3R Apr 20, 2021
0b3e91a
Simpler body handling.
KN4CK3R Apr 20, 2021
f2cbd62
Process files in batches to reduce HTTP calls.
KN4CK3R Apr 20, 2021
3688138
Merge branch 'master' into feature-push-mirror
6543 Apr 21, 2021
656c347
Fixed unit tests.
KN4CK3R Apr 21, 2021
a6088cb
Added created timestamp.
KN4CK3R Apr 21, 2021
1923b51
Fixed name.
KN4CK3R Apr 21, 2021
1eb9c74
Merge branch 'master' of https://github.com/go-gitea/gitea into featu…
KN4CK3R May 1, 2021
5739008
Fixed invalid column name.
KN4CK3R May 12, 2021
980548a
Changed name to prevent xorm auto setting.
KN4CK3R May 12, 2021
310fdb9
Remove table header im empty.
KN4CK3R May 12, 2021
33d0244
Strip exit code from error message.
KN4CK3R May 12, 2021
ffaf55c
Added docs page about mirroring.
KN4CK3R May 13, 2021
180a61c
Fixed date.
KN4CK3R May 18, 2021
835f228
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 18, 2021
b83497b
Use new method.
KN4CK3R May 18, 2021
2a01584
Use jsoniter.
KN4CK3R May 18, 2021
d92a8ba
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 30, 2021
882e958
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 31, 2021
902a730
Fixed merge errors.
KN4CK3R May 31, 2021
68e0c00
Use syncPullMirror in test.
KN4CK3R May 31, 2021
ac879f8
Fixed merge error.
KN4CK3R May 31, 2021
65f47da
Moved test to integrations.
KN4CK3R May 31, 2021
2bc6102
Added push mirror test.
KN4CK3R Jun 1, 2021
d7a3719
Removed NextUpdateUnix.
KN4CK3R Jun 2, 2021
dad9adf
and here
KN4CK3R Jun 2, 2021
c876f05
Merge branch 'main' into feature-push-mirror
6543 Jun 2, 2021
687302a
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jun 5, 2021
c950877
Merge branch 'main' into feature-push-mirror
6543 Jun 6, 2021
c76cdc6
Merge branch 'main' into feature-push-mirror
6543 Jun 7, 2021
ec872c0
Merge branch 'main' into feature-push-mirror
6543 Jun 9, 2021
4e39abb
Apply suggestions from code review
6543 Jun 11, 2021
e482136
Merge branch 'main' into feature-push-mirror
6543 Jun 11, 2021
19d7911
Fixed sql error.
KN4CK3R Jun 11, 2021
444b96c
Added test.
KN4CK3R Jun 11, 2021
27b30d6
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jun 14, 2021
567dccc
untouch models/migrations/v182.go
6543 Jun 14, 2021
c305bca
Merge branch 'main' into feature-push-mirror
6543 Jun 14, 2021
9a66e72
Merge branch 'master' into feature-push-mirror
6543 Jun 14, 2021
2c05692
`xorm:"text"`
6543 Jun 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions docs/content/doc/advanced/repo-mirror.en-us.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
date: "2021-05-13T00:00:00-00:00"
title: "Repository Mirror"
slug: "repo-mirror"
weight: 45
toc: false
draft: false
menu:
sidebar:
parent: "advanced"
name: "Repository Mirror"
weight: 45
identifier: "repo-mirror"
---

# Repository Mirror

Repository mirroring allows for the mirroring of repositories to and from external sources. You can use it to mirror branches, tags, and commits between repositories.

**Table of Contents**

{{< toc >}}

## Use cases

The following are some possible use cases for repository mirroring:

- You migrated to Gitea but still need to keep your project in another source. In that case, you can simply set it up to mirror to Gitea (pull) and all the essential history of commits, tags, and branches are available in your Gitea instance.
- You have old projects in another source that you don’t use actively anymore, but don’t want to remove for archiving purposes. In that case, you can create a push mirror so that your active Gitea repository can push its changes to the old location.

## Pulling from a remote repository

For an existing remote repository, you can set up pull mirroring as follows:

1. Select **New Migration** in the **Create...** menu on the top right.
2. Select the remote repository service.
3. Enter a repository URL.
4. If the repository needs authentication fill in your authentication information.
5. Check the box **This repository will be a mirror**.
5. Select **Migrate repository** to save the configuration.

The repository now gets mirrored periodically from the remote repository. You can force a sync by selecting **Synchronize Now** in the repository settings.

## Pushing to a remote repository

For an existing repository, you can set up push mirroring as follows:

1. In your repository, go to **Settings** > **Repository**, and then the **Mirror Settings** section.
2. Enter a repository URL.
3. If the repository needs authentication expand the **Authorization** section and fill in your authentication information.
4. Select **Add Push Mirror** to save the configuration.

The repository now gets mirrored periodically to the remote repository. You can force a sync by selecting **Synchronize Now**. In case of an error a message displayed to help you resolve it.

:exclamation::exclamation: **NOTE:** This will force push to the remote repository. This will overwrite any changes in the remote repository! :exclamation::exclamation:

### Setting up a push mirror from Gitea to GitHub

To set up a mirror from Gitea to GitHub, you need to follow these steps:

1. Create a [GitHub personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the *public_repo* box checked.
2. Fill in the **Git Remote Repository URL**: `https://github.com/<your_github_group>/<your_github_project>.git`.
3. Fill in the **Authorization** fields with your GitHub username and the personal access token.
4. Select **Add Push Mirror** to save the configuration.

The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.

### Setting up a push mirror from Gitea to GitLab

To set up a mirror from Gitea to GitLab, you need to follow these steps:

1. Create a [GitLab personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) with *write_repository* scope.
2. Fill in the **Git Remote Repository URL**: `https://<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
3. Fill in the **Authorization** fields with `oauth2` as **Username** and your GitLab personal access token as **Password**.
4. Select **Add Push Mirror** to save the configuration.

The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.

### Setting up a push mirror from Gitea to Bitbucket

To set up a mirror from Gitea to Bitbucket, you need to follow these steps:

1. Create a [Bitbucket app password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) with the *Repository Write* box checked.
2. Fill in the **Git Remote Repository URL**: `https://bitbucket.org/<your_bitbucket_group_or_name>/<your_bitbucket_project>.git`.
3. Fill in the **Authorization** fields with your Bitbucket username and the app password as **Password**.
4. Select **Add Push Mirror** to save the configuration.

The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,24 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package mirror
package integrations

import (
"context"
"path/filepath"
"testing"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
migration "code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/repository"
mirror_service "code.gitea.io/gitea/services/mirror"
release_service "code.gitea.io/gitea/services/release"

"github.com/stretchr/testify/assert"
)

func TestMain(m *testing.M) {
models.MainTest(m, filepath.Join("..", ".."))
}

func TestRelease_MirrorDelete(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
func TestMirrorPull(t *testing.T) {
defer prepareTestEnv(t)()

user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
Expand Down Expand Up @@ -76,7 +72,7 @@ func TestRelease_MirrorDelete(t *testing.T) {
err = mirror.GetMirror()
assert.NoError(t, err)

_, ok := runSync(ctx, mirror.Mirror)
ok := mirror_service.SyncPullMirror(ctx, mirror.ID)
assert.True(t, ok)

count, err := models.GetReleaseCountByRepoID(mirror.ID, findOptions)
Expand All @@ -87,7 +83,7 @@ func TestRelease_MirrorDelete(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, release_service.DeleteReleaseByID(release.ID, user, true))

_, ok = runSync(ctx, mirror.Mirror)
ok = mirror_service.SyncPullMirror(ctx, mirror.ID)
assert.True(t, ok)

count, err = models.GetReleaseCountByRepoID(mirror.ID, findOptions)
Expand Down
86 changes: 86 additions & 0 deletions integrations/mirror_push_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2021 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 integrations

import (
"context"
"fmt"
"net/http"
"net/url"
"testing"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
mirror_service "code.gitea.io/gitea/services/mirror"

"github.com/stretchr/testify/assert"
)

func TestMirrorPush(t *testing.T) {
onGiteaRun(t, testMirrorPush)
}

func testMirrorPush(t *testing.T, u *url.URL) {
defer prepareTestEnv(t)()

setting.Migrations.AllowLocalNetworks = true

user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
srcRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)

mirrorRepo, err := repository.CreateRepository(user, user, models.CreateRepoOptions{
Name: "test-push-mirror",
})
assert.NoError(t, err)

ctx := NewAPITestContext(t, user.LowerName, srcRepo.Name)

doCreatePushMirror(ctx, fmt.Sprintf("%s%s/%s", u.String(), url.PathEscape(ctx.Username), url.PathEscape(mirrorRepo.Name)), user.LowerName, userPassword)(t)

mirrors, err := models.GetPushMirrorsByRepoID(srcRepo.ID)
assert.NoError(t, err)
assert.Len(t, mirrors, 1)

ok := mirror_service.SyncPushMirror(context.Background(), mirrors[0].ID)
assert.True(t, ok)

srcGitRepo, err := git.OpenRepository(srcRepo.RepoPath())
assert.NoError(t, err)
defer srcGitRepo.Close()

srcCommit, err := srcGitRepo.GetBranchCommit("master")
assert.NoError(t, err)

mirrorGitRepo, err := git.OpenRepository(mirrorRepo.RepoPath())
assert.NoError(t, err)
defer mirrorGitRepo.Close()

mirrorCommit, err := mirrorGitRepo.GetBranchCommit("master")
assert.NoError(t, err)

assert.Equal(t, srcCommit.ID, mirrorCommit.ID)
}

func doCreatePushMirror(ctx APITestContext, address, username, password string) func(t *testing.T) {
return func(t *testing.T) {
csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)))

req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{
"_csrf": csrf,
"action": "push-mirror-add",
"push_mirror_address": address,
"push_mirror_username": username,
"push_mirror_password": password,
"push_mirror_interval": "0",
})
ctx.Session.MakeRequest(t, req, http.StatusFound)

flashCookie := ctx.Session.GetCookie("macaron_flash")
assert.NotNil(t, flashCookie)
assert.Contains(t, flashCookie.Value, "success")
}
}
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ var migrations = []Migration{
NewMigration("Delete credentials from past migrations", deleteMigrationCredentials),
// v181 -> v182
NewMigration("Always save primary email on email address table", addPrimaryEmail2EmailAddress),
// v182 -> v183
NewMigration("Create PushMirror table", createPushMirrorTable),
}

// GetCurrentDBVersion returns the current db version
Expand Down
2 changes: 1 addition & 1 deletion models/migrations/v180.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func removeCredentials(payload string) (string, error) {

opts.AuthPassword = ""
opts.AuthToken = ""
opts.CloneAddr = util.SanitizeURLCredentials(opts.CloneAddr, true)
opts.CloneAddr = util.NewStringURLSanitizer(opts.CloneAddr, true).Replace(opts.CloneAddr)

confBytes, err := json.Marshal(opts)
if err != nil {
Expand Down
39 changes: 39 additions & 0 deletions models/migrations/v182.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2021 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 migrations

import (
"fmt"
"time"

"code.gitea.io/gitea/modules/timeutil"

"xorm.io/xorm"
)

func createPushMirrorTable(x *xorm.Engine) error {
type PushMirror struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
RemoteName string

Interval time.Duration
CreatedUnix timeutil.TimeStamp `xorm:"created"`
LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
LastError string
}

sess := x.NewSession()
defer sess.Close()
6543 marked this conversation as resolved.
Show resolved Hide resolved
if err := sess.Begin(); err != nil {
return err
}

if err := sess.Sync2(new(PushMirror)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}

return sess.Commit()
}
1 change: 1 addition & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ func init() {
new(ProjectIssue),
new(Session),
new(RepoTransfer),
new(PushMirror),
)

gonicNames := []string{"SSL", "UID"}
Expand Down
27 changes: 20 additions & 7 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,13 @@ type Repository struct {
NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"`
NumOpenProjects int `xorm:"-"`

IsPrivate bool `xorm:"INDEX"`
IsEmpty bool `xorm:"INDEX"`
IsArchived bool `xorm:"INDEX"`
IsMirror bool `xorm:"INDEX"`
*Mirror `xorm:"-"`
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`
IsPrivate bool `xorm:"INDEX"`
IsEmpty bool `xorm:"INDEX"`
IsArchived bool `xorm:"INDEX"`
IsMirror bool `xorm:"INDEX"`
*Mirror `xorm:"-"`
PushMirrors []*PushMirror `xorm:"-"`
Status RepositoryStatus `xorm:"NOT NULL DEFAULT 0"`

RenderingMetas map[string]string `xorm:"-"`
DocumentRenderingMetas map[string]string `xorm:"-"`
Expand Down Expand Up @@ -255,7 +256,12 @@ func (repo *Repository) SanitizedOriginalURL() string {
if repo.OriginalURL == "" {
return ""
}
return util.SanitizeURLCredentials(repo.OriginalURL, false)
u, err := url.Parse(repo.OriginalURL)
if err != nil {
return ""
}
u.User = nil
return u.String()
}

// ColorFormat returns a colored string to represent this repo
Expand Down Expand Up @@ -657,6 +663,12 @@ func (repo *Repository) GetMirror() (err error) {
return err
}

// LoadPushMirrors populates the repository push mirrors.
func (repo *Repository) LoadPushMirrors() (err error) {
repo.PushMirrors, err = GetPushMirrorsByRepoID(repo.ID)
return err
}

// GetBaseRepo populates repo.BaseRepo for a fork repository and
// returns an error on failure (NOTE: no error is returned for
// non-fork repositories, and BaseRepo will be left untouched)
Expand Down Expand Up @@ -1487,6 +1499,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
&Notification{RepoID: repoID},
&ProtectedBranch{RepoID: repoID},
&PullRequest{BaseRepoID: repoID},
&PushMirror{RepoID: repoID},
&Release{RepoID: repoID},
&RepoIndexerStatus{RepoID: repoID},
&RepoRedirect{RedirectRepoID: repoID},
Expand Down
16 changes: 16 additions & 0 deletions models/repo_mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import (
"xorm.io/xorm"
)

// RemoteMirrorer defines base methods for pull/push mirrors.
type RemoteMirrorer interface {
GetRepository() *Repository
GetRemoteName() string
}

// Mirror represents mirror information of a repository.
type Mirror struct {
ID int64 `xorm:"pk autoincr"`
Expand Down Expand Up @@ -52,6 +58,16 @@ func (m *Mirror) AfterLoad(session *xorm.Session) {
}
}

// GetRepository returns the repository.
func (m *Mirror) GetRepository() *Repository {
return m.Repo
}

// GetRemoteName returns the name of the remote.
func (m *Mirror) GetRemoteName() string {
return "origin"
}

// ScheduleNextUpdate calculates and sets next update time.
func (m *Mirror) ScheduleNextUpdate() {
if m.Interval != 0 {
Expand Down
Loading