Skip to content

Commit e51546c

Browse files
committed
fix(git): try to prevent unshallow error 'shallow file has changed since we read it'
1. Added file lock for a fetch-with-unshallow operation. 2. Catch possible error with telemetry event. Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
1 parent 918fed4 commit e51546c

File tree

9 files changed

+101
-16
lines changed

9 files changed

+101
-16
lines changed

pkg/build/conveyor.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -907,8 +907,8 @@ func generateGitMappings(ctx context.Context, imageBaseConfig *config.StapelImag
907907

908908
if isShallowClone {
909909
if c.werfConfig.Meta.GitWorktree.GetAllowUnshallow() {
910-
if err := localGitRepo.FetchOrigin(ctx); err != nil {
911-
return nil, err
910+
if err := localGitRepo.Unshallow(ctx); err != nil {
911+
return nil, fmt.Errorf("unable to fetch local git repo: %w", err)
912912
}
913913
} else {
914914
logboek.Context(ctx).Warn().LogLn("The usage of shallow git clone may break reproducibility and slow down incremental rebuilds.")

pkg/git_repo/git_repo.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ type GitRepo interface {
4343
GetWorkTreeDir() string
4444
RemoteOriginUrl(_ context.Context) (string, error)
4545
IsShallowClone(ctx context.Context) (bool, error)
46-
FetchOrigin(ctx context.Context) error
46+
FetchOrigin(ctx context.Context, opts FetchOptions) error
47+
Unshallow(ctx context.Context) error
4748
SyncWithOrigin(ctx context.Context) error
4849

4950
CreateDetachedMergeCommit(ctx context.Context, fromCommit, toCommit string) (string, error)
@@ -75,6 +76,10 @@ type GitRepo interface {
7576
ValidateStatusResult(ctx context.Context, pathMatcher path_matcher.PathMatcher) error
7677
}
7778

79+
type FetchOptions struct {
80+
Unshallow bool
81+
}
82+
7883
type gitRepo interface {
7984
GitRepo
8085

pkg/git_repo/local.go

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import (
1010

1111
"github.com/go-git/go-git/v5"
1212

13+
"github.com/werf/lockgate"
1314
"github.com/werf/logboek"
1415
"github.com/werf/logboek/pkg/types"
1516
"github.com/werf/werf/pkg/git_repo/repo_handle"
1617
"github.com/werf/werf/pkg/path_matcher"
18+
"github.com/werf/werf/pkg/telemetry"
1719
"github.com/werf/werf/pkg/true_git"
1820
"github.com/werf/werf/pkg/true_git/status"
1921
"github.com/werf/werf/pkg/util"
@@ -156,29 +158,74 @@ func (repo *Local) SyncWithOrigin(ctx context.Context) error {
156158
})
157159
}
158160

159-
func (repo *Local) FetchOrigin(ctx context.Context) error {
161+
func (repo *Local) acquireFetchLock(ctx context.Context) (lockgate.LockHandle, error) {
162+
_, lock, err := werf.AcquireHostLock(ctx, fmt.Sprintf("local_git_repo.fetch.%s", repo.GitDir), lockgate.AcquireOptions{})
163+
return lock, err
164+
}
165+
166+
func (repo *Local) Unshallow(ctx context.Context) error {
167+
if lock, err := repo.acquireFetchLock(ctx); err != nil {
168+
return fmt.Errorf("unable to acquire fetch lock: %w", err)
169+
} else {
170+
defer werf.ReleaseHostLock(lock)
171+
}
172+
160173
isShallow, err := repo.IsShallowClone(ctx)
161174
if err != nil {
162175
return fmt.Errorf("check shallow clone failed: %w", err)
163176
}
177+
if !isShallow {
178+
return nil
179+
}
164180

165-
remoteOriginUrl, err := repo.RemoteOriginUrl(ctx)
181+
err = repo.doFetchOrigin(ctx, true)
166182
if err != nil {
167-
return fmt.Errorf("get remote origin failed: %w", err)
183+
return fmt.Errorf("unable to fetch origin: %w", err)
168184
}
169185

170-
if remoteOriginUrl == "" {
171-
return fmt.Errorf("git remote origin was not detected")
186+
return nil
187+
}
188+
189+
func (repo *Local) FetchOrigin(ctx context.Context, opts FetchOptions) error {
190+
if lock, err := repo.acquireFetchLock(ctx); err != nil {
191+
return fmt.Errorf("unable to acquire fetch lock: %w", err)
192+
} else {
193+
defer werf.ReleaseHostLock(lock)
172194
}
173195

196+
var unshallow bool
197+
if opts.Unshallow {
198+
isShallow, err := repo.IsShallowClone(ctx)
199+
if err != nil {
200+
return fmt.Errorf("check shallow clone failed: %w", err)
201+
}
202+
unshallow = isShallow
203+
}
204+
205+
return repo.doFetchOrigin(ctx, unshallow)
206+
}
207+
208+
func (repo *Local) doFetchOrigin(ctx context.Context, unshallow bool) error {
174209
return logboek.Context(ctx).Default().LogProcess("Fetching origin").DoError(func() error {
210+
remoteOriginUrl, err := repo.RemoteOriginUrl(ctx)
211+
if err != nil {
212+
return fmt.Errorf("get remote origin failed: %w", err)
213+
}
214+
215+
if remoteOriginUrl == "" {
216+
return fmt.Errorf("git remote origin was not detected")
217+
}
218+
175219
fetchOptions := true_git.FetchOptions{
176-
Unshallow: isShallow,
220+
Unshallow: unshallow,
177221
RefSpecs: map[string]string{"origin": "+refs/heads/*:refs/remotes/origin/*"},
178222
}
179223

180224
if err := true_git.Fetch(ctx, repo.WorkTreeDir, fetchOptions); err != nil {
181-
return fmt.Errorf("fetch failed: %w", err)
225+
if true_git.IsShallowFileChangedSinceWeReadIt(err) {
226+
telemetry.GetTelemetryWerfIO().UnshallowFailed(ctx, err)
227+
}
228+
return err
182229
}
183230

184231
return nil

pkg/git_repo/remote.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func (repo *Remote) CloneAndFetch(ctx context.Context) error {
107107
return nil
108108
}
109109

110-
return repo.FetchOrigin(ctx)
110+
return repo.FetchOrigin(ctx, FetchOptions{})
111111
}
112112

113113
func (repo *Remote) isCloneExists() (bool, error) {
@@ -210,7 +210,11 @@ func (repo *Remote) SyncWithOrigin(ctx context.Context) error {
210210
panic("not implemented")
211211
}
212212

213-
func (repo *Remote) FetchOrigin(ctx context.Context) error {
213+
func (repo *Remote) Unshallow(ctx context.Context) error {
214+
panic("not implemented")
215+
}
216+
217+
func (repo *Remote) FetchOrigin(ctx context.Context, opts FetchOptions) error {
214218
if repo.IsDryRun {
215219
return nil
216220
}

pkg/telemetry/event.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package telemetry
22

3+
import "os"
4+
35
type EventType string
46

57
const (
6-
CommandStartedEvent EventType = "CommandStarted"
7-
CommandExitedEvent EventType = "CommandExited"
8+
CommandStartedEvent EventType = "CommandStarted"
9+
CommandExitedEvent EventType = "CommandExited"
10+
UnshallowFailedEvent EventType = "UnshallowFailed"
811
)
912

1013
type Event interface {
@@ -38,3 +41,19 @@ type CommandExited struct {
3841
}
3942

4043
func (e *CommandExited) GetType() EventType { return CommandExitedEvent }
44+
45+
func NewUnshallowFailed(errorMessage string) *UnshallowFailed {
46+
return &UnshallowFailed{
47+
ErrorMessage: errorMessage,
48+
GitlabRunnerVersion: os.Getenv("CI_RUNNER_VERSION"),
49+
GitlabServerVersion: os.Getenv("CI_SERVER_VERSION"),
50+
}
51+
}
52+
53+
type UnshallowFailed struct {
54+
ErrorMessage string `json:"errorMessage"`
55+
GitlabRunnerVersion string `json:"gitlabRunnerVersion"`
56+
GitlabServerVersion string `json:"gitlabServerVersion"`
57+
}
58+
59+
func (*UnshallowFailed) GetType() EventType { return UnshallowFailedEvent }

pkg/telemetry/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func NewTraceExporter(url string) (*otlptrace.Exporter, error) {
2525
otlptracehttp.WithEndpoint(urlObj.Host),
2626
otlptracehttp.WithURLPath(urlObj.Path),
2727
otlptracehttp.WithRetry(otlptracehttp.RetryConfig{Enabled: false}),
28-
otlptracehttp.WithTimeout(800*time.Millisecond),
28+
otlptracehttp.WithTimeout(1300*time.Millisecond),
2929
)
3030

3131
client := otlptracehttp.NewClient(opts...)

pkg/telemetry/no_telemetrywerfio.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ func (t *NoTelemetryWerfIO) SetProjectID(context.Context, string)
1010
func (t *NoTelemetryWerfIO) SetCommand(context.Context, string) {}
1111
func (t *NoTelemetryWerfIO) CommandExited(context.Context, int) {}
1212
func (t *NoTelemetryWerfIO) SetCommandOptions(context.Context, []CommandOption) {}
13+
func (t *NoTelemetryWerfIO) UnshallowFailed(context.Context, error) {}

pkg/telemetry/telemetrywerfio.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type TelemetryWerfIOInterface interface {
3333

3434
CommandStarted(ctx context.Context)
3535
CommandExited(ctx context.Context, exitCode int)
36+
UnshallowFailed(ctx context.Context, err error)
3637
}
3738

3839
type TelemetryWerfIO struct {
@@ -63,7 +64,7 @@ func NewTelemetryWerfIO(url string, opts TelemetryWerfIOOptions) (*TelemetryWerf
6364
tracerProvider: sdktrace.NewTracerProvider(
6465
sdktrace.WithBatcher(e,
6566
sdktrace.WithBatchTimeout(1*time.Millisecond), // send all available events immediately
66-
sdktrace.WithExportTimeout(800*time.Millisecond),
67+
sdktrace.WithExportTimeout(1300*time.Millisecond),
6768
),
6869
),
6970
traceExporter: e,
@@ -132,6 +133,10 @@ func (t *TelemetryWerfIO) CommandExited(ctx context.Context, exitCode int) {
132133
t.sendEvent(ctx, NewCommandExited(exitCode, int64(duration/time.Millisecond)))
133134
}
134135

136+
func (t *TelemetryWerfIO) UnshallowFailed(ctx context.Context, err error) {
137+
t.sendEvent(ctx, NewUnshallowFailed(err.Error()))
138+
}
139+
135140
func (t *TelemetryWerfIO) getAttributes() map[string]interface{} {
136141
attributes := map[string]interface{}{
137142
"version": werf.Version,

pkg/true_git/repository.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ type FetchOptions struct {
2525
RefSpecs map[string]string
2626
}
2727

28+
func IsShallowFileChangedSinceWeReadIt(err error) bool {
29+
return err != nil && strings.Contains(err.Error(), "shallow file has changed since we read it")
30+
}
31+
2832
func Fetch(ctx context.Context, path string, options FetchOptions) error {
2933
commandArgs := []string{"fetch"}
3034

0 commit comments

Comments
 (0)