Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5c01fc0
add safe mirror option
jimmy201602 Mar 22, 2022
571ef30
fix confilict
jimmy201602 Mar 22, 2022
4bdb918
remove translation and chinese comment
jimmy201602 Mar 22, 2022
1dc838e
add migrate for safe mirror function
jimmy201602 Mar 22, 2022
a9cd187
add model migration file
jimmy201602 Mar 22, 2022
a8066a5
remove chinese comment
jimmy201602 Mar 22, 2022
d46a5de
replace fmt errorf
jimmy201602 Mar 23, 2022
93c5f50
format the migration code
jimmy201602 Mar 23, 2022
724030a
remove unclear statement code
jimmy201602 Mar 23, 2022
ed8b20f
remove some unnecessary code
jimmy201602 Mar 23, 2022
f602aab
remove some unnecessary code
jimmy201602 Mar 23, 2022
f02d5b3
lint the code
jimmy201602 Mar 23, 2022
44e4912
lint the code
jimmy201602 Mar 23, 2022
9b40dee
lint the code
jimmy201602 Mar 23, 2022
5e16d8a
lint the code
jimmy201602 Mar 23, 2022
aaaac62
lint the code
jimmy201602 Mar 23, 2022
a7eb9cf
abstract the code to function
jimmy201602 Mar 23, 2022
6801ef1
abstract the code to function
jimmy201602 Mar 23, 2022
e324609
format the code
jimmy201602 Mar 23, 2022
fff683b
format the code
jimmy201602 Mar 23, 2022
061b46a
abstract the code to function
jimmy201602 Mar 23, 2022
e3b10d2
abstract the code to function
jimmy201602 Mar 23, 2022
ad9a062
lint the code
jimmy201602 Mar 23, 2022
8b7479a
lint the code
jimmy201602 Mar 23, 2022
b703f0a
fix the lint error
jimmy201602 Mar 23, 2022
a1cb58f
reorganize the code
jimmy201602 Mar 23, 2022
e68e5c6
fix push mirror safe option bug
jimmy201602 Mar 23, 2022
0ec287c
fix push mirror safe option bug
jimmy201602 Mar 23, 2022
8f78236
lint the code and rewrite the suggestion for log
jimmy201602 Mar 25, 2022
bf243ee
Update migrations.go
jimmy201602 Jun 7, 2022
0bf7a7c
merge the latest code
jimmy201602 Jun 7, 2022
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
4 changes: 4 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ var migrations = []Migration{

// v211 -> v212
NewMigration("Create ForeignReference table", createForeignReferenceTable),

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

// v212 -> v213
NewMigration("Add package tables", addPackageTables),
// v213 -> v214
Expand All @@ -387,6 +388,9 @@ var migrations = []Migration{
NewMigration("Add auto merge table", addAutoMergeTable),
// v215 -> v216
NewMigration("allow to view files in PRs", addReviewViewedFiles),

// v216 -> v217
NewMigration("Add safe mirrors", addEnableSafeMirrorColToMirror),
}

// GetCurrentDBVersion returns the current db version
Expand Down
22 changes: 22 additions & 0 deletions models/migrations/v222.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 migrations

import (
"fmt"

"xorm.io/xorm"
)

func addEnableSafeMirrorColToMirror(x *xorm.Engine) error {
type Mirror struct {
EnableSafeMirror bool `xorm:"NOT NULL DEFAULT false"`
}

if err := x.Sync2(new(Mirror)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}
11 changes: 6 additions & 5 deletions models/repo/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ type RemoteMirrorer interface {

// Mirror represents mirror information of a repository.
type Mirror struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Repo *Repository `xorm:"-"`
Interval time.Duration
EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Repo *Repository `xorm:"-"`
Interval time.Duration
EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
EnableSafeMirror bool `xorm:"NOT NULL DEFAULT false"`

UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"`
NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"`
Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,8 @@ mirror_last_synced = Last Synchronized
mirror_password_placeholder = (Unchanged)
mirror_password_blank_placeholder = (Unset)
mirror_password_help = Change the username to erase a stored password.
mirror_enable_safe = Safe Mirror
mirror_enable_safe_desc = Enable safe mirroring to avoid deleting local data such as branches or tags once they are deleted remotely
watchers = Watchers
stargazers = Stargazers
forks = Forks
Expand Down
6 changes: 3 additions & 3 deletions options/locale/locale_zh-CN.ini
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,8 @@ last_used=上次使用在
no_activity=没有最近活动
can_read_info=读取
can_write_info=写入
key_state_desc=7 天内使用过该密钥
token_state_desc=7 天内使用过该密钥
key_state_desc=7 天内使用过该密钥
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert changes to translation file

token_state_desc=7 天内使用过该密钥
principal_state_desc=7 天内使用过该规则
show_openid=在个人信息上显示
hide_openid=在个人信息上隐藏
Expand Down Expand Up @@ -878,7 +878,7 @@ watchers=关注者
stargazers=称赞者
forks=派生仓库
pick_reaction=选择你的表情
reactions_more=再加载 %d
reactions_more=再加载 %d
unit_disabled=站点管理员已禁用此仓库单元。
language_other=其它
adopt_search=输入用户名以搜索未被收录的仓库... (留空以查找全部)
Expand Down
5 changes: 5 additions & 0 deletions routers/web/repo/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ func Settings(ctx *context.Context) {
ctx.Data["SigningKeyAvailable"] = len(signing) > 0
ctx.Data["SigningSettings"] = setting.Repository.Signing
ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

mirror, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
ctx.Data["EnableSafeMirror"] = err == nil && mirror.EnableSafeMirror

if ctx.Doer.IsAdmin {
if setting.Indexer.RepoIndexerEnabled {
status, err := repo_model.GetIndexerStatus(ctx, ctx.Repo.Repository, repo_model.RepoIndexerTypeCode)
Expand Down Expand Up @@ -193,6 +197,7 @@ func SettingsPost(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
} else {
ctx.Repo.Mirror.EnablePrune = form.EnablePrune
ctx.Repo.Mirror.EnableSafeMirror = form.EnableSafeMirror
ctx.Repo.Mirror.Interval = interval
ctx.Repo.Mirror.ScheduleNextUpdate()
if err := repo_model.UpdateMirror(ctx, ctx.Repo.Mirror); err != nil {
Expand Down
1 change: 1 addition & 0 deletions services/forms/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ type RepoSettingForm struct {
Private bool
Template bool
EnablePrune bool
EnableSafeMirror bool

// Advanced settings
EnableWiki bool
Expand Down
13 changes: 13 additions & 0 deletions services/mirror/mirror_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,19 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
log.Error("SyncMirrors [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
}

if m.EnableSafeMirror {
// detect can safe mirror
canUpdate, err := detectCanUpdateMirror(ctx, m, gitArgs)
Copy link
Member

@6543 6543 Mar 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be resource intense on huge repos .. e.g. Linux kernel

  • git binary do not provide a option to filter the update ... :/

if err != nil {
log.Error("CheckRepositoryCanSafeMirrorError: %v", err)
}
// can not safe mirror
if !canUpdate {
log.Error("CheckSyncMirrors [repo: %-v]: cannot sync safe mirror...", m.Repo)
return nil, false
}
}

stdoutBuilder := strings.Builder{}
stderrBuilder := strings.Builder{}
if err := git.NewCommand(ctx, gitArgs...).
Expand Down
203 changes: 203 additions & 0 deletions services/mirror/mirror_pull_safe_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright 2019 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 mirror

import (
"context"
"fmt"
"strconv"
"strings"
"time"

admin_model "code.gitea.io/gitea/models/admin"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

// get git command running stdout and stderr
func getGitCommandStdoutStderr(ctx context.Context, m *repo_model.Mirror, gitArgs []string, newRepoPath string) (string, string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a better naming, this sounds like a generic function, but it's specific to a certain action.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also a better comment, please.
The comment does not help to understand what this function is doing.
Also, I just noticed that the comment does not start with the obligatory method name
(// getGitCommandStdoutStderr ...)

stdoutBuilder := strings.Builder{}
stderrBuilder := strings.Builder{}
Comment on lines +24 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How big can these become? If they can become quite big, it's better to use buffered I/O.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second

remoteAddr, remoteErr := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if remoteErr != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
return "", "", remoteErr
}
Comment on lines +28 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
remoteAddr, remoteErr := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if remoteErr != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
return "", "", remoteErr
}
remoteAddr, remoteErr := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if remoteErr != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
return "", "", remoteErr
}
Suggested change
remoteAddr, remoteErr := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if remoteErr != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
return "", "", remoteErr
}
remoteAddr, err := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if err != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, err)
return "", "", err
}


if err := git.NewCommand(ctx, gitArgs...).
SetDescription(fmt.Sprintf("Mirror.getMirrorCanUpdate: %s", m.Repo.FullName())).
RunWithContext(&git.RunContext{
Timeout: timeout,
Dir: newRepoPath,
Stdout: &stdoutBuilder,
Stderr: &stderrBuilder,
}); err != nil {
stdout := stdoutBuilder.String()
stderr := stderrBuilder.String()
sanitizer := util.NewURLSanitizer(remoteAddr, true)
stderrMessage := sanitizer.Replace(stderr)
stdoutMessage := sanitizer.Replace(stdout)
log.Error("CreateRepositoryNotice: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incorrect here.

log.Error("getGitCommandStdoutStderr [repo: %-v]: failed to check if mirror can be updated:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
desc := fmt.Sprintf("Failed to check if mirror '%s' can be updated: %s", newRepoPath, stderrMessage)
if err = admin_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to return here with a error.

}
stdoutRepoCommitCount := stdoutBuilder.String()
stderrRepoCommitCount := stdoutBuilder.String()
stderrBuilder.Reset()
stdoutBuilder.Reset()

return stdoutRepoCommitCount, stderrRepoCommitCount, nil
}

// sync new repo mirror
func syncRepoMirror(ctx context.Context, m *repo_model.Mirror, gitArgs []string, newRepoPath string) (bool, error) {
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
remoteAddr, remoteErr := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if remoteErr != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
Comment on lines +65 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
remoteAddr, remoteErr := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if remoteErr != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
remoteAddr, err := git.GetRemoteAddress(ctx, newRepoPath, m.GetRemoteName())
if err != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: GetRemoteAddress Error %v", m.Repo, err)

}
stdoutBuilder := strings.Builder{}
stderrBuilder := strings.Builder{}
err := git.NewCommand(ctx, gitArgs...).
SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())).
RunWithContext(&git.RunContext{
Timeout: timeout,
Dir: newRepoPath,
Stdout: &stdoutBuilder,
Stderr: &stderrBuilder,
})
sanitizer := util.NewURLSanitizer(remoteAddr, true)
var stdout, stderr string
if err != nil {
stdout = stdoutBuilder.String()
stderr = stderrBuilder.String()

// sanitize the output, since it may contain the remote address, which may
// contain a password
stderrMessage := sanitizer.Replace(stderr)
stdoutMessage := sanitizer.Replace(stdout)

// Now check if the error is a resolve reference due to broken reference
if strings.Contains(stderr, "unable to resolve reference") && strings.Contains(stderr, "reference broken") {
log.Warn("SyncMirrors [repo: %-v]: failed to update mirror repository due to broken references:\nStdout: %s\nStderr: %s\nErr: %v\nAttempting Prune", m.Repo, stdoutMessage, stderrMessage, err)
err = nil

// Attempt prune
pruneErr := pruneBrokenReferences(ctx, m, newRepoPath, timeout, &stdoutBuilder, &stderrBuilder, sanitizer, false)
if pruneErr == nil {
// Successful prune - reattempt mirror
stderrBuilder.Reset()
stdoutBuilder.Reset()
if err = git.NewCommand(ctx, gitArgs...).
SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())).
RunWithContext(&git.RunContext{
Timeout: timeout,
Dir: newRepoPath,
Stdout: &stdoutBuilder,
Stderr: &stderrBuilder,
}); err != nil {
stdout := stdoutBuilder.String()
stderr := stderrBuilder.String()

// sanitize the output, since it may contain the remote address, which may
// contain a password
stderrMessage = sanitizer.Replace(stderr)
stdoutMessage = sanitizer.Replace(stdout)
}
}
}

// If there is still an error (or there always was an error)
if err != nil {
log.Error("SyncMirrors [repo: %-v]: failed to update mirror repository:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", newRepoPath, stderrMessage)
if err = admin_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return false, nil
}
}
return false, nil
}

// detect user can update the mirror
func detectCanUpdateMirror(ctx context.Context, m *repo_model.Mirror, gitArgs []string) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function needs some more comments in order to not have a 10 minutes hard time understanding how this function is achieving it's "detection method".

repoPath := m.Repo.RepoPath()
newRepoPath := fmt.Sprintf("%s_update", repoPath)

// do copy directory recursive
err := util.CopyDir(repoPath, newRepoPath)
defer func() {
// delete the temp directory
errDelete := util.RemoveAll(newRepoPath)
if errDelete != nil {
log.Error("DeleteRepositoryTempDirectoryError: %v", errDelete)
}
}()
if err != nil {
log.Error("GetMirrorCanUpdate [repo: %-v]: CopyDirectory Error %v", m.Repo, err)
return false, err
}
syncStatus, err := syncRepoMirror(ctx, m, gitArgs, newRepoPath)
if err != nil {
return false, err
}
if !syncStatus {
return false, nil
}
gitCommitCountArgs := []string{"rev-list", "HEAD", "--count"}
stdoutNewRepoCommitCount, _, err := getGitCommandStdoutStderr(ctx, m, gitCommitCountArgs, newRepoPath)
if err != nil {
return false, err
}
stdoutNewRepoCommitCount = strings.TrimSpace(stdoutNewRepoCommitCount)
stdoutRepoCommitCount, _, err := getGitCommandStdoutStderr(ctx, m, gitCommitCountArgs, repoPath)
if err != nil {
return false, err
}
stdoutRepoCommitCount = strings.TrimSpace(stdoutRepoCommitCount)
var repoCommitCount, newRepoCommitCount int64
if i, err := strconv.ParseInt(stdoutRepoCommitCount, 10, 64); err == nil {
repoCommitCount = i
} else {
return false, err
}
if i, err := strconv.ParseInt(stdoutNewRepoCommitCount, 10, 64); err == nil {
newRepoCommitCount = i
} else {
return false, err
}
if repoCommitCount > newRepoCommitCount {
return false, nil
} else if repoCommitCount == newRepoCommitCount {
// noting to happen
return true, nil
} else {
// compare commit id
skipcout := newRepoCommitCount - repoCommitCount
gitNewRepoLastCommitIDArgs := []string{"log", "-1", fmt.Sprintf("--skip=%d", skipcout), "--format='%H'"}
stdoutNewRepoCommitID, _, err := getGitCommandStdoutStderr(ctx, m, gitNewRepoLastCommitIDArgs, newRepoPath)
if err != nil {
return false, err
}
gitRepoLastCommitIDArgs := []string{"log", "--format='%H'", "-n", "1"}
stdoutRepoCommitID, _, err := getGitCommandStdoutStderr(ctx, m, gitRepoLastCommitIDArgs, repoPath)
if err != nil {
return false, err
}
if stdoutNewRepoCommitID != stdoutRepoCommitID {
return false, fmt.Errorf("Old repo commit id: %s not match new repo id: %s", stdoutRepoCommitID, stdoutNewRepoCommitID)
}
}
return true, nil
Comment on lines +180 to +202
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for the sake of understanding here, it's more or less trying to detect if a force-push has happen on the upstream's repo?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the purpose. However, I think copying the repo again and again is not ideal.

A better solution could be: track all branches locally, fetch remote, compare the merge-base, if the merge-base is not the locally tracked one, then there is a force-push.

And there might be other better solutions, but copying the repo is not the proper way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, I'm also looking for an ideal solution to figure out this sitution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you seem my option with the reference-transaction hook ? #14076 (comment)
You'll also need an option to allow/disallow force-push on specific branches otherwise mirrors will break quickly (people force-push on PR branches)

}
7 changes: 7 additions & 0 deletions templates/repo/settings/options.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@
<label>{{.i18n.Tr "repo.mirror_prune_desc"}}</label>
</div>
</div>
<div class="inline field {{if .Err_EnableSafeMirror}}error{{end}}">
<label>{{.i18n.Tr "repo.mirror_enable_safe"}}</label>
<div class="ui checkbox">
<input id="enable_safe_mirror" name="enable_safe_mirror" type="checkbox" {{if .EnableSafeMirror}}checked{{end}}>
<label>{{.i18n.Tr "repo.mirror_enable_safe_desc"}}</label>
</div>
</div>
<div class="inline field {{if .Err_Interval}}error{{end}}">
<label for="interval">{{.i18n.Tr "repo.mirror_interval"}}</label>
<input id="interval" name="interval" value="{{.MirrorInterval}}">
Expand Down