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
base: main
Are you sure you want to change the base?
Conversation
@@ -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 天内使用过该密钥 |
There was a problem hiding this comment.
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
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), | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -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) |
There was a problem hiding this comment.
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 ... :/
options/locale/locale_en-US.ini
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
routers/web/repo/setting.go
Outdated
mirrors, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID) | ||
if err != nil { | ||
ctx.Data["EnableSafeMirror"] = false | ||
} else { | ||
ctx.Data["EnableSafeMirror"] = mirrors.EnableSafeMirror | ||
} |
There was a problem hiding this comment.
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 | |
} | |
mirror, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID) | |
if err != nil { | |
ctx.Data["EnableSafeMirror"] = false | |
} else { | |
ctx.Data["EnableSafeMirror"] = mirror.EnableSafeMirror | |
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | |
} | |
mirror, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID) | |
ctx.Data["EnableSafeMirror"] = err == nil && mirror.EnableSafeMirror |
services/mirror/mirror_pull.go
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
safeMirror := m.EnableSafeMirror |
services/mirror/mirror_pull.go
Outdated
@@ -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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if safeMirror { | |
if m.EnableSafeMirror { |
services/mirror/mirror_pull.go
Outdated
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) | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) | ||
} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
models/migrations/v222.go
Outdated
"xorm.io/xorm" | ||
) | ||
|
||
func addColorColToMirror(x *xorm.Engine) error { |
There was a problem hiding this comment.
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?
options/locale/locale_en-US.ini
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
models/migrations/migrations.go
Outdated
@@ -378,6 +378,9 @@ var migrations = []Migration{ | |||
|
|||
// v211 -> v212 | |||
NewMigration("Create ForeignReference table", createForeignReferenceTable), | |||
|
|||
// v212 -> v213 | |||
NewMigration("Add new safe mirror option", addColorColToMirror), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NewMigration("Add new safe mirror option", addColorColToMirror), | |
NewMigration("Add safe pull mirrors", addColorColToMirror), |
stdoutBuilder := strings.Builder{} | ||
stderrBuilder := strings.Builder{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to https://github.com/go-gitea/gitea/pull/19165/files#r833702091 I'd say large.
routers/web/repo/setting.go
Outdated
mirrors, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID) | ||
if err != nil { | ||
ctx.Data["EnableSafeMirror"] = false | ||
} else { | ||
ctx.Data["EnableSafeMirror"] = mirrors.EnableSafeMirror | ||
} |
There was a problem hiding this comment.
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 | |
} | |
mirror, err := repo_model.GetMirrorByRepoID(ctx.Repo.Repository.ID) | |
ctx.Data["EnableSafeMirror"] = err == nil && mirror.EnableSafeMirror |
services/mirror/mirror_pull.go
Outdated
} | ||
// can not safe mirror | ||
if !canUpdate { | ||
log.Error("CheckSyncMirrors [repo: %-v]: can not safe mirror...", m.Repo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log.Error("CheckSyncMirrors [repo: %-v]: can not safe mirror...", m.Repo) | |
log.Error("CheckSyncMirrors [repo: %-v]: cannot sync safe mirror...", m.Repo) |
services/mirror/mirror_pull.go
Outdated
newRepoPath := fmt.Sprintf("%s_update", repoPath) | ||
// delete the temp directory | ||
errDelete := util.RemoveAll(newRepoPath) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
I will fix these errors later. |
add safe pull mirror option to avoid original repository delete
close #14076