Skip to content

Commit 053b2f4

Browse files
zeripath6543techknowlogicklunny
authored
Handle broken references in mirror sync (#17013)
* Handle broken references in mirror sync If there are broken references during a mirror attempt to fix using `git remote prune`. Signed-off-by: Andrew Thornton <[email protected]> Co-authored-by: 6543 <[email protected]> Co-authored-by: techknowlogick <[email protected]> Co-authored-by: Lunny Xiao <[email protected]>
1 parent 2add8fe commit 053b2f4

File tree

1 file changed

+105
-13
lines changed

1 file changed

+105
-13
lines changed

services/mirror/mirror_pull.go

+105-13
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,43 @@ func parseRemoteUpdateOutput(output string) []*mirrorSyncResult {
141141
return results
142142
}
143143

144+
func pruneBrokenReferences(ctx context.Context,
145+
m *models.Mirror,
146+
repoPath string,
147+
timeout time.Duration,
148+
stdoutBuilder, stderrBuilder *strings.Builder,
149+
sanitizer *strings.Replacer,
150+
isWiki bool) error {
151+
152+
wiki := ""
153+
if isWiki {
154+
wiki = "Wiki "
155+
}
156+
157+
stderrBuilder.Reset()
158+
stdoutBuilder.Reset()
159+
pruneErr := git.NewCommandContext(ctx, "remote", "prune", m.GetRemoteName()).
160+
SetDescription(fmt.Sprintf("Mirror.runSync %ssPrune references: %s ", wiki, m.Repo.FullName())).
161+
RunInDirTimeoutPipeline(timeout, repoPath, stdoutBuilder, stderrBuilder)
162+
if pruneErr != nil {
163+
stdout := stdoutBuilder.String()
164+
stderr := stderrBuilder.String()
165+
166+
// sanitize the output, since it may contain the remote address, which may
167+
// contain a password
168+
stderrMessage := sanitizer.Replace(stderr)
169+
stdoutMessage := sanitizer.Replace(stdout)
170+
171+
log.Error("Failed to prune mirror repository %s%-v references:\nStdout: %s\nStderr: %s\nErr: %v", wiki, m.Repo, stdoutMessage, stderrMessage, pruneErr)
172+
desc := fmt.Sprintf("Failed to prune mirror repository %s'%s' references: %s", wiki, repoPath, stderrMessage)
173+
if err := models.CreateRepositoryNotice(desc); err != nil {
174+
log.Error("CreateRepositoryNotice: %v", err)
175+
}
176+
// this if will only be reached on a successful prune so try to get the mirror again
177+
}
178+
return pruneErr
179+
}
180+
144181
// runSync returns true if sync finished without error.
145182
func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool) {
146183
repoPath := m.Repo.RepoPath()
@@ -161,25 +198,52 @@ func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool)
161198

162199
stdoutBuilder := strings.Builder{}
163200
stderrBuilder := strings.Builder{}
164-
if err := git.NewCommand(gitArgs...).
201+
if err := git.NewCommandContext(ctx, gitArgs...).
165202
SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())).
166203
RunInDirTimeoutPipeline(timeout, repoPath, &stdoutBuilder, &stderrBuilder); err != nil {
167204
stdout := stdoutBuilder.String()
168205
stderr := stderrBuilder.String()
169206

170207
// sanitize the output, since it may contain the remote address, which may
171208
// contain a password
172-
173209
sanitizer := util.NewURLSanitizer(remoteAddr, true)
174210
stderrMessage := sanitizer.Replace(stderr)
175211
stdoutMessage := sanitizer.Replace(stdout)
176212

177-
log.Error("Failed to update mirror repository %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
178-
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, stderrMessage)
179-
if err = models.CreateRepositoryNotice(desc); err != nil {
180-
log.Error("CreateRepositoryNotice: %v", err)
213+
// Now check if the error is a resolve reference due to broken reference
214+
if strings.Contains(stderr, "unable to resolve reference") && strings.Contains(stderr, "reference broken") {
215+
log.Warn("Failed to update mirror repository %-v due to broken references:\nStdout: %s\nStderr: %s\nErr: %v\nAttempting Prune", m.Repo, stdoutMessage, stderrMessage, err)
216+
err = nil
217+
218+
// Attempt prune
219+
pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, sanitizer, false)
220+
if pruneErr == nil {
221+
// Successful prune - reattempt mirror
222+
stderrBuilder.Reset()
223+
stdoutBuilder.Reset()
224+
if err = git.NewCommandContext(ctx, gitArgs...).
225+
SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())).
226+
RunInDirTimeoutPipeline(timeout, repoPath, &stdoutBuilder, &stderrBuilder); err != nil {
227+
stdout := stdoutBuilder.String()
228+
stderr := stderrBuilder.String()
229+
230+
// sanitize the output, since it may contain the remote address, which may
231+
// contain a password
232+
stderrMessage = sanitizer.Replace(stderr)
233+
stdoutMessage = sanitizer.Replace(stdout)
234+
}
235+
}
236+
}
237+
238+
// If there is still an error (or there always was an error)
239+
if err != nil {
240+
log.Error("Failed to update mirror repository %-v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
241+
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, stderrMessage)
242+
if err = models.CreateRepositoryNotice(desc); err != nil {
243+
log.Error("CreateRepositoryNotice: %v", err)
244+
}
245+
return nil, false
181246
}
182-
return nil, false
183247
}
184248
output := stderrBuilder.String()
185249

@@ -212,7 +276,7 @@ func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool)
212276
log.Trace("SyncMirrors [repo: %-v Wiki]: running git remote update...", m.Repo)
213277
stderrBuilder.Reset()
214278
stdoutBuilder.Reset()
215-
if err := git.NewCommand("remote", "update", "--prune", m.GetRemoteName()).
279+
if err := git.NewCommandContext(ctx, "remote", "update", "--prune", m.GetRemoteName()).
216280
SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())).
217281
RunInDirTimeoutPipeline(timeout, wikiPath, &stdoutBuilder, &stderrBuilder); err != nil {
218282
stdout := stdoutBuilder.String()
@@ -226,16 +290,44 @@ func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool)
226290
log.Error("GetRemoteAddress Error %v", remoteErr)
227291
}
228292

293+
// sanitize the output, since it may contain the remote address, which may
294+
// contain a password
229295
sanitizer := util.NewURLSanitizer(remoteAddr, true)
230296
stderrMessage := sanitizer.Replace(stderr)
231297
stdoutMessage := sanitizer.Replace(stdout)
232298

233-
log.Error("Failed to update mirror repository wiki %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
234-
desc := fmt.Sprintf("Failed to update mirror repository wiki '%s': %s", wikiPath, stderrMessage)
235-
if err = models.CreateRepositoryNotice(desc); err != nil {
236-
log.Error("CreateRepositoryNotice: %v", err)
299+
// Now check if the error is a resolve reference due to broken reference
300+
if strings.Contains(stderrMessage, "unable to resolve reference") && strings.Contains(stderrMessage, "reference broken") {
301+
log.Warn("Failed to update mirror wiki repository %-v due to broken references:\nStdout: %s\nStderr: %s\nErr: %v\nAttempting Prune", m.Repo, stdoutMessage, stderrMessage, err)
302+
err = nil
303+
304+
// Attempt prune
305+
pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, sanitizer, true)
306+
if pruneErr == nil {
307+
// Successful prune - reattempt mirror
308+
stderrBuilder.Reset()
309+
stdoutBuilder.Reset()
310+
311+
if err = git.NewCommandContext(ctx, "remote", "update", "--prune", m.GetRemoteName()).
312+
SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())).
313+
RunInDirTimeoutPipeline(timeout, wikiPath, &stdoutBuilder, &stderrBuilder); err != nil {
314+
stdout := stdoutBuilder.String()
315+
stderr := stderrBuilder.String()
316+
stderrMessage = sanitizer.Replace(stderr)
317+
stdoutMessage = sanitizer.Replace(stdout)
318+
}
319+
}
320+
}
321+
322+
// If there is still an error (or there always was an error)
323+
if err != nil {
324+
log.Error("Failed to update mirror repository wiki %-v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
325+
desc := fmt.Sprintf("Failed to update mirror repository wiki '%s': %s", wikiPath, stderrMessage)
326+
if err = models.CreateRepositoryNotice(desc); err != nil {
327+
log.Error("CreateRepositoryNotice: %v", err)
328+
}
329+
return nil, false
237330
}
238-
return nil, false
239331
}
240332
log.Trace("SyncMirrors [repo: %-v Wiki]: git remote update complete", m.Repo)
241333
}

0 commit comments

Comments
 (0)