Skip to content

Commit 5a52c76

Browse files
authored
Merge branch 'main' into fix-16147
2 parents 5502a48 + 15fbf23 commit 5a52c76

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2552
-883
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
date: "2021-05-13T00:00:00-00:00"
3+
title: "Repository Mirror"
4+
slug: "repo-mirror"
5+
weight: 45
6+
toc: false
7+
draft: false
8+
menu:
9+
sidebar:
10+
parent: "advanced"
11+
name: "Repository Mirror"
12+
weight: 45
13+
identifier: "repo-mirror"
14+
---
15+
16+
# Repository Mirror
17+
18+
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.
19+
20+
**Table of Contents**
21+
22+
{{< toc >}}
23+
24+
## Use cases
25+
26+
The following are some possible use cases for repository mirroring:
27+
28+
- 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.
29+
- 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.
30+
31+
## Pulling from a remote repository
32+
33+
For an existing remote repository, you can set up pull mirroring as follows:
34+
35+
1. Select **New Migration** in the **Create...** menu on the top right.
36+
2. Select the remote repository service.
37+
3. Enter a repository URL.
38+
4. If the repository needs authentication fill in your authentication information.
39+
5. Check the box **This repository will be a mirror**.
40+
5. Select **Migrate repository** to save the configuration.
41+
42+
The repository now gets mirrored periodically from the remote repository. You can force a sync by selecting **Synchronize Now** in the repository settings.
43+
44+
## Pushing to a remote repository
45+
46+
For an existing repository, you can set up push mirroring as follows:
47+
48+
1. In your repository, go to **Settings** > **Repository**, and then the **Mirror Settings** section.
49+
2. Enter a repository URL.
50+
3. If the repository needs authentication expand the **Authorization** section and fill in your authentication information.
51+
4. Select **Add Push Mirror** to save the configuration.
52+
53+
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.
54+
55+
:exclamation::exclamation: **NOTE:** This will force push to the remote repository. This will overwrite any changes in the remote repository! :exclamation::exclamation:
56+
57+
### Setting up a push mirror from Gitea to GitHub
58+
59+
To set up a mirror from Gitea to GitHub, you need to follow these steps:
60+
61+
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.
62+
2. Fill in the **Git Remote Repository URL**: `https://github.com/<your_github_group>/<your_github_project>.git`.
63+
3. Fill in the **Authorization** fields with your GitHub username and the personal access token.
64+
4. Select **Add Push Mirror** to save the configuration.
65+
66+
The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.
67+
68+
### Setting up a push mirror from Gitea to GitLab
69+
70+
To set up a mirror from Gitea to GitLab, you need to follow these steps:
71+
72+
1. Create a [GitLab personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) with *write_repository* scope.
73+
2. Fill in the **Git Remote Repository URL**: `https://<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
74+
3. Fill in the **Authorization** fields with `oauth2` as **Username** and your GitLab personal access token as **Password**.
75+
4. Select **Add Push Mirror** to save the configuration.
76+
77+
The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.
78+
79+
### Setting up a push mirror from Gitea to Bitbucket
80+
81+
To set up a mirror from Gitea to Bitbucket, you need to follow these steps:
82+
83+
1. Create a [Bitbucket app password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) with the *Repository Write* box checked.
84+
2. Fill in the **Git Remote Repository URL**: `https://bitbucket.org/<your_bitbucket_group_or_name>/<your_bitbucket_project>.git`.
85+
3. Fill in the **Authorization** fields with your Bitbucket username and the app password as **Password**.
86+
4. Select **Add Push Mirror** to save the configuration.
87+
88+
The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.

services/mirror/mirror_test.go renamed to integrations/mirror_pull_test.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,24 @@
22
// Use of this source code is governed by a MIT-style
33
// license that can be found in the LICENSE file.
44

5-
package mirror
5+
package integrations
66

77
import (
88
"context"
9-
"path/filepath"
109
"testing"
1110

1211
"code.gitea.io/gitea/models"
1312
"code.gitea.io/gitea/modules/git"
1413
migration "code.gitea.io/gitea/modules/migrations/base"
1514
"code.gitea.io/gitea/modules/repository"
15+
mirror_service "code.gitea.io/gitea/services/mirror"
1616
release_service "code.gitea.io/gitea/services/release"
1717

1818
"github.com/stretchr/testify/assert"
1919
)
2020

21-
func TestMain(m *testing.M) {
22-
models.MainTest(m, filepath.Join("..", ".."))
23-
}
24-
25-
func TestRelease_MirrorDelete(t *testing.T) {
26-
assert.NoError(t, models.PrepareTestDatabase())
21+
func TestMirrorPull(t *testing.T) {
22+
defer prepareTestEnv(t)()
2723

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

79-
_, ok := runSync(ctx, mirror.Mirror)
75+
ok := mirror_service.SyncPullMirror(ctx, mirror.ID)
8076
assert.True(t, ok)
8177

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

90-
_, ok = runSync(ctx, mirror.Mirror)
86+
ok = mirror_service.SyncPullMirror(ctx, mirror.ID)
9187
assert.True(t, ok)
9288

9389
count, err = models.GetReleaseCountByRepoID(mirror.ID, findOptions)

integrations/mirror_push_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"net/http"
11+
"net/url"
12+
"testing"
13+
14+
"code.gitea.io/gitea/models"
15+
"code.gitea.io/gitea/modules/git"
16+
"code.gitea.io/gitea/modules/repository"
17+
"code.gitea.io/gitea/modules/setting"
18+
mirror_service "code.gitea.io/gitea/services/mirror"
19+
20+
"github.com/stretchr/testify/assert"
21+
)
22+
23+
func TestMirrorPush(t *testing.T) {
24+
onGiteaRun(t, testMirrorPush)
25+
}
26+
27+
func testMirrorPush(t *testing.T, u *url.URL) {
28+
defer prepareTestEnv(t)()
29+
30+
setting.Migrations.AllowLocalNetworks = true
31+
32+
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
33+
srcRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
34+
35+
mirrorRepo, err := repository.CreateRepository(user, user, models.CreateRepoOptions{
36+
Name: "test-push-mirror",
37+
})
38+
assert.NoError(t, err)
39+
40+
ctx := NewAPITestContext(t, user.LowerName, srcRepo.Name)
41+
42+
doCreatePushMirror(ctx, fmt.Sprintf("%s%s/%s", u.String(), url.PathEscape(ctx.Username), url.PathEscape(mirrorRepo.Name)), user.LowerName, userPassword)(t)
43+
44+
mirrors, err := models.GetPushMirrorsByRepoID(srcRepo.ID)
45+
assert.NoError(t, err)
46+
assert.Len(t, mirrors, 1)
47+
48+
ok := mirror_service.SyncPushMirror(context.Background(), mirrors[0].ID)
49+
assert.True(t, ok)
50+
51+
srcGitRepo, err := git.OpenRepository(srcRepo.RepoPath())
52+
assert.NoError(t, err)
53+
defer srcGitRepo.Close()
54+
55+
srcCommit, err := srcGitRepo.GetBranchCommit("master")
56+
assert.NoError(t, err)
57+
58+
mirrorGitRepo, err := git.OpenRepository(mirrorRepo.RepoPath())
59+
assert.NoError(t, err)
60+
defer mirrorGitRepo.Close()
61+
62+
mirrorCommit, err := mirrorGitRepo.GetBranchCommit("master")
63+
assert.NoError(t, err)
64+
65+
assert.Equal(t, srcCommit.ID, mirrorCommit.ID)
66+
}
67+
68+
func doCreatePushMirror(ctx APITestContext, address, username, password string) func(t *testing.T) {
69+
return func(t *testing.T) {
70+
csrf := GetCSRF(t, ctx.Session, fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)))
71+
72+
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{
73+
"_csrf": csrf,
74+
"action": "push-mirror-add",
75+
"push_mirror_address": address,
76+
"push_mirror_username": username,
77+
"push_mirror_password": password,
78+
"push_mirror_interval": "0",
79+
})
80+
ctx.Session.MakeRequest(t, req, http.StatusFound)
81+
82+
flashCookie := ctx.Session.GetCookie("macaron_flash")
83+
assert.NotNil(t, flashCookie)
84+
assert.Contains(t, flashCookie.Value, "success")
85+
}
86+
}

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ var migrations = []Migration{
315315
NewMigration("Always save primary email on email address table", addPrimaryEmail2EmailAddress),
316316
// v182 -> v183
317317
NewMigration("Add issue resource index table", addIssueResourceIndexTable),
318+
// v183 -> v184
319+
NewMigration("Create PushMirror table", createPushMirrorTable),
318320
}
319321

320322
// GetCurrentDBVersion returns the current db version

models/migrations/v180.go

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
package migrations
66

77
import (
8-
"code.gitea.io/gitea/models"
9-
"code.gitea.io/gitea/modules/migrations/base"
10-
"code.gitea.io/gitea/modules/structs"
118
"code.gitea.io/gitea/modules/util"
129

1310
jsoniter "github.com/json-iterator/go"
@@ -16,20 +13,38 @@ import (
1613
)
1714

1815
func deleteMigrationCredentials(x *xorm.Engine) (err error) {
16+
// Task represents a task
17+
type Task struct {
18+
ID int64
19+
DoerID int64 `xorm:"index"` // operator
20+
OwnerID int64 `xorm:"index"` // repo owner id, when creating, the repoID maybe zero
21+
RepoID int64 `xorm:"index"`
22+
Type int
23+
Status int `xorm:"index"`
24+
StartTime int64
25+
EndTime int64
26+
PayloadContent string `xorm:"TEXT"`
27+
Errors string `xorm:"TEXT"` // if task failed, saved the error reason
28+
Created int64 `xorm:"created"`
29+
}
30+
31+
const TaskTypeMigrateRepo = 0
32+
const TaskStatusStopped = 2
33+
1934
const batchSize = 100
2035

2136
// only match migration tasks, that are not pending or running
2237
cond := builder.Eq{
23-
"type": structs.TaskTypeMigrateRepo,
38+
"type": TaskTypeMigrateRepo,
2439
}.And(builder.Gte{
25-
"status": structs.TaskStatusStopped,
40+
"status": TaskStatusStopped,
2641
})
2742

2843
sess := x.NewSession()
2944
defer sess.Close()
3045

3146
for start := 0; ; start += batchSize {
32-
tasks := make([]*models.Task, 0, batchSize)
47+
tasks := make([]*Task, 0, batchSize)
3348
if err = sess.Limit(batchSize, start).Where(cond, 0).Find(&tasks); err != nil {
3449
return
3550
}
@@ -55,7 +70,41 @@ func deleteMigrationCredentials(x *xorm.Engine) (err error) {
5570
}
5671

5772
func removeCredentials(payload string) (string, error) {
58-
var opts base.MigrateOptions
73+
// MigrateOptions defines the way a repository gets migrated
74+
// this is for internal usage by migrations module and func who interact with it
75+
type MigrateOptions struct {
76+
// required: true
77+
CloneAddr string `json:"clone_addr" binding:"Required"`
78+
CloneAddrEncrypted string `json:"clone_addr_encrypted,omitempty"`
79+
AuthUsername string `json:"auth_username"`
80+
AuthPassword string `json:"-"`
81+
AuthPasswordEncrypted string `json:"auth_password_encrypted,omitempty"`
82+
AuthToken string `json:"-"`
83+
AuthTokenEncrypted string `json:"auth_token_encrypted,omitempty"`
84+
// required: true
85+
UID int `json:"uid" binding:"Required"`
86+
// required: true
87+
RepoName string `json:"repo_name" binding:"Required"`
88+
Mirror bool `json:"mirror"`
89+
LFS bool `json:"lfs"`
90+
LFSEndpoint string `json:"lfs_endpoint"`
91+
Private bool `json:"private"`
92+
Description string `json:"description"`
93+
OriginalURL string
94+
GitServiceType int
95+
Wiki bool
96+
Issues bool
97+
Milestones bool
98+
Labels bool
99+
Releases bool
100+
Comments bool
101+
PullRequests bool
102+
ReleaseAssets bool
103+
MigrateToRepoID int64
104+
MirrorInterval string `json:"mirror_interval"`
105+
}
106+
107+
var opts MigrateOptions
59108
json := jsoniter.ConfigCompatibleWithStandardLibrary
60109
err := json.Unmarshal([]byte(payload), &opts)
61110
if err != nil {
@@ -64,7 +113,7 @@ func removeCredentials(payload string) (string, error) {
64113

65114
opts.AuthPassword = ""
66115
opts.AuthToken = ""
67-
opts.CloneAddr = util.SanitizeURLCredentials(opts.CloneAddr, true)
116+
opts.CloneAddr = util.NewStringURLSanitizer(opts.CloneAddr, true).Replace(opts.CloneAddr)
68117

69118
confBytes, err := json.Marshal(opts)
70119
if err != nil {

models/migrations/v183.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
"time"
10+
11+
"code.gitea.io/gitea/modules/timeutil"
12+
13+
"xorm.io/xorm"
14+
)
15+
16+
func createPushMirrorTable(x *xorm.Engine) error {
17+
type PushMirror struct {
18+
ID int64 `xorm:"pk autoincr"`
19+
RepoID int64 `xorm:"INDEX"`
20+
RemoteName string
21+
22+
Interval time.Duration
23+
CreatedUnix timeutil.TimeStamp `xorm:"created"`
24+
LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
25+
LastError string `xorm:"text"`
26+
}
27+
28+
sess := x.NewSession()
29+
defer sess.Close()
30+
if err := sess.Begin(); err != nil {
31+
return err
32+
}
33+
34+
if err := sess.Sync2(new(PushMirror)); err != nil {
35+
return fmt.Errorf("Sync2: %v", err)
36+
}
37+
38+
return sess.Commit()
39+
}

models/models.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ func init() {
135135
new(Session),
136136
new(RepoTransfer),
137137
new(IssueIndex),
138+
new(PushMirror),
138139
)
139140

140141
gonicNames := []string{"SSL", "UID"}

0 commit comments

Comments
 (0)