@@ -39,6 +39,18 @@ type (
3939 RemoteCommits []string
4040 IsOk bool
4141 }
42+
43+ // repositoryUploadHooks captures repository upload test seams for a single upload attempt.
44+ repositoryUploadHooks struct {
45+ // uploadRepositoryChanges replaces the full upload path when tests need to bypass git operations.
46+ uploadRepositoryChanges func () (int64 , error )
47+ // getSearchCommits retrieves the local/remote commit comparison for the current repository state.
48+ getSearchCommits func () (* searchCommitsResponse , error )
49+ // unshallowGitRepository expands shallow clones before computing the final missing commits.
50+ unshallowGitRepository func () (bool , error )
51+ // sendObjectsPackFile uploads a git packfile containing the missing commits.
52+ sendObjectsPackFile func (string , []string , []string ) (int64 , error )
53+ }
4254)
4355
4456var (
5163 // additionalFeaturesInitializationMu serializes additional feature initialization with test-only state resets.
5264 additionalFeaturesInitializationMu sync.Mutex
5365
66+ // repositoryUploadHooksMu protects repository upload hooks that tests replace while settings upload work may still be running.
67+ repositoryUploadHooksMu sync.RWMutex
68+
5469 // ciVisibilityRapidClient contains the http rapid client to do CI Visibility queries and upload to the rapid backend
5570 ciVisibilityClient net.Client
5671
7590 // ciVisibilityImpactedTestsAnalyzer contains the CI Visibility impacted tests analyzer
7691 ciVisibilityImpactedTestsAnalyzer * impactedtests.ImpactedTestAnalyzer
7792
78- // uploadRepositoryChangesFunc is a must-not-call test seam used to prove offline/file modes suppress git upload.
79- uploadRepositoryChangesFunc = uploadRepositoryChanges
93+ // uploadRepositoryChangesFunc is a must-not-call test seam used to prove offline/file modes suppress git upload. A nil value uses the default git upload path.
94+ uploadRepositoryChangesFunc func () ( int64 , error )
8095
8196 // getSearchCommitsFunc allows tests to exercise repository upload control flow without reading local git state.
8297 getSearchCommitsFunc = getSearchCommits
@@ -107,12 +122,14 @@ func ensureSettingsInitialization(serviceName string) {
107122 log .Debug ("civisibility: settings initialization mode [manifest:%t payload_files:%t manifest_file:%s payload_root:%s repository_upload_enabled:%t]" ,
108123 testOptimizationMode .ManifestEnabled , testOptimizationMode .PayloadFilesEnabled , bazel .TestOptimizationPathForLog (testOptimizationMode .ManifestPath ), testOptimizationMode .PayloadsRoot , uploadEnabled )
109124 if uploadEnabled {
125+ repositoryUpload := snapshotRepositoryUploadHooks ()
126+
110127 // upload the repository changes
111128 go func () {
112129 defer func () {
113130 close (uploadChannel )
114131 }()
115- bytes , err := uploadRepositoryChangesFunc ()
132+ bytes , err := repositoryUpload . run ()
116133 if err != nil {
117134 log .Error ("civisibility: error uploading repository changes: %s" , err .Error ())
118135 } else {
@@ -404,10 +421,36 @@ func GetImpactedTestsAnalyzer() *impactedtests.ImpactedTestAnalyzer {
404421 return ciVisibilityImpactedTestsAnalyzer
405422}
406423
424+ // snapshotRepositoryUploadHooks returns a stable hook set so asynchronous upload work is isolated from test resets.
425+ func snapshotRepositoryUploadHooks () repositoryUploadHooks {
426+ repositoryUploadHooksMu .RLock ()
427+ defer repositoryUploadHooksMu .RUnlock ()
428+
429+ return repositoryUploadHooks {
430+ uploadRepositoryChanges : uploadRepositoryChangesFunc ,
431+ getSearchCommits : getSearchCommitsFunc ,
432+ unshallowGitRepository : unshallowGitRepositoryFunc ,
433+ sendObjectsPackFile : sendObjectsPackFileFunc ,
434+ }
435+ }
436+
437+ // run executes either a full upload replacement hook or the default upload path with the captured hooks.
438+ func (hooks repositoryUploadHooks ) run () (int64 , error ) {
439+ if hooks .uploadRepositoryChanges != nil {
440+ return hooks .uploadRepositoryChanges ()
441+ }
442+ return uploadRepositoryChangesWithHooks (hooks )
443+ }
444+
407445// uploadRepositoryChanges discovers the commits and packfiles that must be uploaded so backend features can reason about the current repo state.
408446func uploadRepositoryChanges () (bytes int64 , err error ) {
447+ return uploadRepositoryChangesWithHooks (snapshotRepositoryUploadHooks ())
448+ }
449+
450+ // uploadRepositoryChangesWithHooks runs the default git upload flow against a stable hook snapshot.
451+ func uploadRepositoryChangesWithHooks (hooks repositoryUploadHooks ) (bytes int64 , err error ) {
409452 // get the search commits response
410- initialCommitData , err := getSearchCommitsFunc ()
453+ initialCommitData , err := hooks . getSearchCommits ()
411454 if err != nil {
412455 return 0 , fmt .Errorf ("civisibility: error getting the search commits response: %s" , err )
413456 }
@@ -436,7 +479,7 @@ func uploadRepositoryChanges() (bytes int64, err error) {
436479 }
437480
438481 // there's some missing commits on the backend, first we need to check if we need to unshallow before sending anything...
439- hasBeenUnshallowed , err := unshallowGitRepositoryFunc ()
482+ hasBeenUnshallowed , err := hooks . unshallowGitRepository ()
440483 if err != nil || ! hasBeenUnshallowed {
441484 if err != nil {
442485 log .Warn ("%s" , err .Error ())
@@ -445,11 +488,11 @@ func uploadRepositoryChanges() (bytes int64, err error) {
445488 // the initial commit data
446489
447490 // send the pack file with the missing commits
448- return sendObjectsPackFileFunc (initialCommitData .LocalCommits [0 ], initialMissingCommits , initialCommitData .RemoteCommits )
491+ return hooks . sendObjectsPackFile (initialCommitData .LocalCommits [0 ], initialMissingCommits , initialCommitData .RemoteCommits )
449492 }
450493
451494 // after unshallowing the repository we need to get the search commits to calculate the missing commits again
452- commitsData , err := getSearchCommitsFunc ()
495+ commitsData , err := hooks . getSearchCommits ()
453496 if err != nil {
454497 return 0 , fmt .Errorf ("civisibility: error getting the search commits response: %s" , err )
455498 }
@@ -460,7 +503,7 @@ func uploadRepositoryChanges() (bytes int64, err error) {
460503 }
461504
462505 // send the pack file with the missing commits
463- return sendObjectsPackFileFunc (commitsData .LocalCommits [0 ], commitsData .missingCommits (), commitsData .RemoteCommits )
506+ return hooks . sendObjectsPackFile (commitsData .LocalCommits [0 ], commitsData .missingCommits (), commitsData .RemoteCommits )
464507}
465508
466509// getSearchCommits gets the search commits response with the local and remote commits
0 commit comments