The Wayback Machine - https://web.archive.org/web/20220507131306/https://github.com/go-gitea/gitea/pull/19165
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 safe pull mirror option to avoid original repository deletion by force-push #19165

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

Copy link

@jimmy201602 jimmy201602 commented Mar 22, 2022

add safe pull mirror option to avoid original repository delete

close #14076

@lunny lunny added this to the 1.17.0 milestone Mar 22, 2022
@@ -690,8 +690,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

@lafriks lafriks Mar 22, 2022

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

models/repo/mirror.go Outdated Show resolved Hide resolved
@lafriks
Copy link

@lafriks lafriks commented Mar 22, 2022

Also need to add database migration to add Sync changed struct with database table structure

@@ -378,6 +378,9 @@ var migrations = []Migration{

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

Copy link
Member

@KN4CK3R KN4CK3R Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change

@@ -204,6 +205,25 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
log.Error("SyncMirrors [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
}

if safeMirror {
// 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 ... :/

@@ -869,6 +869,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 mirror to avoid delete local repository data
Copy link
Member

@Gusted Gusted Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
mirror_enable_safe_desc = Enable safe mirror to avoid delete local repository data
mirror_enable_safe_desc = Enable safe mirroring to avoid deleting data from the local repository

mirrors, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
if err != nil {
ctx.Data["EnableSafeMirror"] = false
} else {
ctx.Data["EnableSafeMirror"] = mirrors.EnableSafeMirror
}
Copy link
Member

@Gusted Gusted Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
mirrors, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
if err != nil {
ctx.Data["EnableSafeMirror"] = false
} else {
ctx.Data["EnableSafeMirror"] = mirrors.EnableSafeMirror
}
mirror, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
if err != nil {
ctx.Data["EnableSafeMirror"] = false
} else {
ctx.Data["EnableSafeMirror"] = mirror.EnableSafeMirror
}

Copy link
Member

@Gusted Gusted Mar 23, 2022

Choose a reason for hiding this comment

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

I think that we should check if err is ErrMirrorNotExist and allow such error(without returning a 404), otherwise log the error and render a 404 page, because it would then be a database error.

Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
mirrors, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
if err != nil {
ctx.Data["EnableSafeMirror"] = false
} else {
ctx.Data["EnableSafeMirror"] = mirrors.EnableSafeMirror
}
mirror, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
ctx.Data["EnableSafeMirror"] = err == nil && mirror.EnableSafeMirror

@@ -190,6 +190,7 @@ func pruneBrokenReferences(ctx context.Context,
func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bool) {
repoPath := m.Repo.RepoPath()
wikiPath := m.Repo.WikiPath()
safeMirror := m.EnableSafeMirror
Copy link
Member

@Gusted Gusted Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
safeMirror := m.EnableSafeMirror

@@ -204,6 +205,25 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
log.Error("SyncMirrors [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
}

if safeMirror {
Copy link
Member

@Gusted Gusted Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
if safeMirror {
if m.EnableSafeMirror {

canUpdate, err := detectCanUpdateMirror(ctx, m, gitArgs)
newRepoPath := fmt.Sprintf("%s_update", repoPath)
// delete the temp directory
errDelete := util.RemoveAll(newRepoPath)
if errDelete != nil {
log.Error("DeleteRepositoryTempDirectoryError: %v", errDelete)
}
if err != nil {
log.Error("CheckRepositoryCanSafeMirrorError: %v", err)
}
Copy link
Member

@Gusted Gusted Mar 23, 2022

Choose a reason for hiding this comment

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

Why is the error checked later here? In 100% cases you want to check the error immediately.

Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Also, I'd say that every single method call inside these error logs is wrong.

desc := fmt.Sprintf("Failed to get mirror repository can update '%s': %s", newRepoPath, stderrMessage)
log.Error("GetMirrorCanUpdate [repo: %-v]: failed to get mirror repository can update:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
Copy link
Member

@Gusted Gusted Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
desc := fmt.Sprintf("Failed to get mirror repository can update '%s': %s", newRepoPath, stderrMessage)
log.Error("GetMirrorCanUpdate [repo: %-v]: failed to get mirror repository can update:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
log.Error("GetMirrorCanUpdate [repo: %-v]: failed to get mirror repository can update:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
desc := fmt.Sprintf("Failed to get mirror repository can update '%s': %s", newRepoPath, stderrMessage)

More logical to re-order this.

Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
desc := fmt.Sprintf("Failed to get mirror repository can update '%s': %s", newRepoPath, stderrMessage)
log.Error("GetMirrorCanUpdate [repo: %-v]: failed to get mirror repository can update:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
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)

log.Error("GetMirrorCanUpdate [repo: %-v]: failed to get mirror repository can update:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
if err = admin_model.CreateRepositoryNotice(desc); err != nil {
log.Error("GetMirrorCanUpdateNotice: %v", err)
}
Copy link
Member

@Gusted Gusted Mar 23, 2022

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.

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

@Gusted Gusted Mar 23, 2022

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)

}

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

@Gusted Gusted Mar 23, 2022

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".

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
Copy link
Member

@Gusted Gusted Mar 23, 2022

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

@wxiaoguang wxiaoguang Mar 24, 2022

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

@jimmy201602 jimmy201602 Mar 25, 2022

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
Contributor

@42wim 42wim Apr 9, 2022

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)

"xorm.io/xorm"
)

func addColorColToMirror(x *xorm.Engine) error {
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

I think that method name is not correct anymore?

@@ -869,6 +869,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 mirror to avoid delete local repository data
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
mirror_enable_safe_desc = Enable safe mirror to avoid delete local repository data
mirror_enable_safe_desc = Enable safe mirroring to avoid deleting local data such as branches or tags once they are deleted remotely

@@ -378,6 +378,9 @@ var migrations = []Migration{

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

// v212 -> v213
NewMigration("Add new safe mirror option", addColorColToMirror),
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
NewMigration("Add new safe mirror option", addColorColToMirror),
NewMigration("Add safe pull mirrors", addColorColToMirror),

stdoutBuilder := strings.Builder{}
stderrBuilder := strings.Builder{}
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

mirrors, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
if err != nil {
ctx.Data["EnableSafeMirror"] = false
} else {
ctx.Data["EnableSafeMirror"] = mirrors.EnableSafeMirror
}
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
mirrors, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
if err != nil {
ctx.Data["EnableSafeMirror"] = false
} else {
ctx.Data["EnableSafeMirror"] = mirrors.EnableSafeMirror
}
mirror, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID)
ctx.Data["EnableSafeMirror"] = err == nil && mirror.EnableSafeMirror

}
// can not safe mirror
if !canUpdate {
log.Error("CheckSyncMirrors [repo: %-v]: can not safe mirror...", m.Repo)
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
log.Error("CheckSyncMirrors [repo: %-v]: can not safe mirror...", m.Repo)
log.Error("CheckSyncMirrors [repo: %-v]: cannot sync safe mirror...", m.Repo)

newRepoPath := fmt.Sprintf("%s_update", repoPath)
// delete the temp directory
errDelete := util.RemoveAll(newRepoPath)
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

What temp directory?
As that wasn't present previously, I'd assume that it is constructed within detectCanUpdateMirror.
In that case I'd recommend a defer inside the method instead.

)

// 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

@delvh delvh Mar 23, 2022

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 ...)

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

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
log.Error("GetMirrorCanUpdateNotice: %v", err)
log.Error("CreateRepositoryNotice: %v", err)

desc := fmt.Sprintf("Failed to get mirror repository can update '%s': %s", newRepoPath, stderrMessage)
log.Error("GetMirrorCanUpdate [repo: %-v]: failed to get mirror repository can update:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
Copy link
Contributor

@delvh delvh Mar 23, 2022

Choose a reason for hiding this comment

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

Suggested change
desc := fmt.Sprintf("Failed to get mirror repository can update '%s': %s", newRepoPath, stderrMessage)
log.Error("GetMirrorCanUpdate [repo: %-v]: failed to get mirror repository can update:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
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)

@wxiaoguang wxiaoguang changed the title add safe pull mirror option to avoid original repository delete Add safe pull mirror option to avoid original repository deletion by force-push Mar 24, 2022
@jimmy201602
Copy link
Author

@jimmy201602 jimmy201602 commented Mar 24, 2022

I will fix these errors later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

10 participants