@@ -15,6 +15,7 @@ import (
1515 "testing"
1616
1717 "github.com/go-git/go-git/v5"
18+ "github.com/go-git/go-git/v5/plumbing"
1819 "github.com/go-git/go-git/v5/plumbing/object"
1920 "github.com/open-policy-agent/opa-control-plane/internal/config"
2021 "github.com/open-policy-agent/opa-control-plane/internal/gitsync"
@@ -114,6 +115,298 @@ func TestGitsyncLocal(t *testing.T) {
114115 }
115116}
116117
118+ // TestGitsyncLocalMainBranch tests the specific bug where using refs/heads/main
119+ // causes subsequent updates to not fetch new commits due to refspec mismatch.
120+ func TestGitsyncLocalMainBranch (t * testing.T ) {
121+ // Create a git repository to use for testing.
122+ testRepositoryPath := t .TempDir () + "/testing"
123+ repository , err := git .PlainInit (testRepositoryPath , false )
124+ if err != nil {
125+ t .Fatalf ("expected no error while initializing test repository: %v" , err )
126+ }
127+
128+ // Get worktree
129+ w , err := repository .Worktree ()
130+ if err != nil {
131+ t .Fatalf ("expected no error while getting worktree: %v" , err )
132+ }
133+
134+ // Create initial commit on master/main
135+ err = os .WriteFile (testRepositoryPath + "/README" , []byte ("first commit" ), 0644 )
136+ if err != nil {
137+ t .Fatalf ("expected no error while creating new file: %v" , err )
138+ }
139+
140+ _ , err = w .Add ("README" )
141+ if err != nil {
142+ t .Fatalf ("expected no error while adding file to worktree: %v" , err )
143+ }
144+
145+ firstCommit , err := w .Commit ("first" , & git.CommitOptions {Author : & object.Signature {}})
146+ if err != nil {
147+ t .Fatalf ("expected no error while committing changes: %v" , err )
148+ }
149+
150+ // Create main branch pointing to the commit (go-git defaults to master)
151+ headRef , err := repository .Head ()
152+ if err != nil {
153+ t .Fatalf ("expected no error getting HEAD: %v" , err )
154+ }
155+
156+ if err := w .Checkout (& git.CheckoutOptions {
157+ Branch : "refs/heads/main" ,
158+ Hash : headRef .Hash (),
159+ Create : true ,
160+ }); err != nil {
161+ t .Fatalf ("expected no error creating main branch: %v" , err )
162+ }
163+
164+ // Create a new synchronizer with an empty directory to clone a repository into.
165+ clonedRepositoryPath := t .TempDir () + "/test-repo"
166+
167+ ref := "refs/heads/main"
168+ s := gitsync .New (clonedRepositoryPath , config.Git {
169+ Repo : testRepositoryPath ,
170+ Reference : & ref ,
171+ Commit : nil ,
172+ }, "test-source" )
173+
174+ ctx := t .Context ()
175+ meta1 , err := s .Execute (ctx )
176+ if err != nil {
177+ t .Fatalf ("expected no error on first execute, got %v" , err )
178+ }
179+
180+ // Verify the first commit hash
181+ if meta1 ["commit" ] != firstCommit .String () {
182+ t .Fatalf ("expected first commit hash %s, got %s" , firstCommit .String (), meta1 ["commit" ])
183+ }
184+
185+ data , err := os .ReadFile (clonedRepositoryPath + "/README" )
186+ if err != nil {
187+ t .Fatalf ("expected no error while reading file, got: %v" , err )
188+ }
189+
190+ if string (data ) != "first commit" {
191+ t .Fatalf ("expected file content to be 'first commit', got: %s" , string (data ))
192+ }
193+
194+ // Inspect what refs were created after the clone
195+ clonedRepo , err := git .PlainOpen (clonedRepositoryPath )
196+ if err != nil {
197+ t .Fatalf ("expected no error opening cloned repo: %v" , err )
198+ }
199+
200+ refs , err := clonedRepo .References ()
201+ if err != nil {
202+ t .Fatalf ("expected no error getting references: %v" , err )
203+ }
204+
205+ t .Log ("Refs after initial clone:" )
206+ err = refs .ForEach (func (ref * plumbing.Reference ) error {
207+ t .Logf (" %s -> %s" , ref .Name (), ref .Hash ())
208+ return nil
209+ })
210+ if err != nil {
211+ t .Fatalf ("expected no error iterating references: %v" , err )
212+ }
213+
214+ // Now push a second commit to the main branch
215+ err = os .WriteFile (testRepositoryPath + "/README" , []byte ("second commit" ), 0644 )
216+ if err != nil {
217+ t .Fatalf ("expected no error while creating new file: %v" , err )
218+ }
219+
220+ _ , err = w .Add ("README" )
221+ if err != nil {
222+ t .Fatalf ("expected no error while adding file to worktree: %v" , err )
223+ }
224+
225+ secondCommit , err := w .Commit ("second" , & git.CommitOptions {Author : & object.Signature {}})
226+ if err != nil {
227+ t .Fatalf ("expected no error while committing changes: %v" , err )
228+ }
229+
230+ t .Logf ("Source repo now has second commit: %s" , secondCommit .String ())
231+
232+ // Execute again - this should fetch and checkout the new commit
233+ meta2 , err := s .Execute (ctx )
234+ if err != nil {
235+ t .Fatalf ("expected no error on second execute, got %v" , err )
236+ }
237+
238+ // Inspect what refs exist after the second sync
239+ refs , err = clonedRepo .References ()
240+ if err != nil {
241+ t .Fatalf ("expected no error getting references: %v" , err )
242+ }
243+
244+ t .Log ("Refs after second sync:" )
245+ err = refs .ForEach (func (ref * plumbing.Reference ) error {
246+ t .Logf (" %s -> %s" , ref .Name (), ref .Hash ())
247+ return nil
248+ })
249+ if err != nil {
250+ t .Fatalf ("expected no error iterating references: %v" , err )
251+ }
252+
253+ // Verify the second commit hash
254+ if meta2 ["commit" ] != secondCommit .String () {
255+ t .Fatalf ("expected second commit hash %s, got %s" , secondCommit .String (), meta2 ["commit" ])
256+ }
257+
258+ // Verify file contents were updated
259+ data , err = os .ReadFile (clonedRepositoryPath + "/README" )
260+ if err != nil {
261+ t .Fatalf ("expected no error while reading file, got: %v" , err )
262+ }
263+
264+ if string (data ) != "second commit" {
265+ t .Fatalf ("expected file content to be 'second commit', got: %s" , string (data ))
266+ }
267+
268+ head , err := clonedRepo .Head ()
269+ if err != nil {
270+ t .Fatalf ("expected no error getting HEAD: %v" , err )
271+ }
272+
273+ if head .Hash ().String () != secondCommit .String () {
274+ t .Fatalf ("expected HEAD to be at second commit %s, got %s" , secondCommit .String (), head .Hash ().String ())
275+ }
276+ }
277+
278+ // TestGitsyncLocalTag tests syncing to a git tag reference.
279+ func TestGitsyncLocalTag (t * testing.T ) {
280+ testRepositoryPath := t .TempDir () + "/testing"
281+ repository , err := git .PlainInit (testRepositoryPath , false )
282+ if err != nil {
283+ t .Fatalf ("expected no error while initializing test repository: %v" , err )
284+ }
285+
286+ w , err := repository .Worktree ()
287+ if err != nil {
288+ t .Fatalf ("expected no error while getting worktree: %v" , err )
289+ }
290+
291+ // Create first commit
292+ err = os .WriteFile (testRepositoryPath + "/README" , []byte ("v1.0 content" ), 0644 )
293+ if err != nil {
294+ t .Fatalf ("expected no error while creating new file: %v" , err )
295+ }
296+
297+ _ , err = w .Add ("README" )
298+ if err != nil {
299+ t .Fatalf ("expected no error while adding file to worktree: %v" , err )
300+ }
301+
302+ firstCommit , err := w .Commit ("first" , & git.CommitOptions {Author : & object.Signature {}})
303+ if err != nil {
304+ t .Fatalf ("expected no error while committing changes: %v" , err )
305+ }
306+
307+ // Create tag v1.0
308+ _ , err = repository .CreateTag ("v1.0" , firstCommit , nil )
309+ if err != nil {
310+ t .Fatalf ("expected no error creating tag: %v" , err )
311+ }
312+
313+ // Create second commit
314+ err = os .WriteFile (testRepositoryPath + "/README" , []byte ("v2.0 content" ), 0644 )
315+ if err != nil {
316+ t .Fatalf ("expected no error while creating new file: %v" , err )
317+ }
318+
319+ _ , err = w .Add ("README" )
320+ if err != nil {
321+ t .Fatalf ("expected no error while adding file to worktree: %v" , err )
322+ }
323+
324+ secondCommit , err := w .Commit ("second" , & git.CommitOptions {Author : & object.Signature {}})
325+ if err != nil {
326+ t .Fatalf ("expected no error while committing changes: %v" , err )
327+ }
328+
329+ // Create tag v2.0
330+ _ , err = repository .CreateTag ("v2.0" , secondCommit , nil )
331+ if err != nil {
332+ t .Fatalf ("expected no error creating tag: %v" , err )
333+ }
334+
335+ // Sync to tag v1.0
336+ clonedRepositoryPath := t .TempDir () + "/test-repo"
337+ ref := "refs/tags/v1.0"
338+ s := gitsync .New (clonedRepositoryPath , config.Git {
339+ Repo : testRepositoryPath ,
340+ Reference : & ref ,
341+ Commit : nil ,
342+ }, "test-source" )
343+
344+ ctx := t .Context ()
345+ meta1 , err := s .Execute (ctx )
346+ if err != nil {
347+ t .Fatalf ("expected no error on first execute, got %v" , err )
348+ }
349+
350+ if meta1 ["commit" ] != firstCommit .String () {
351+ t .Fatalf ("expected first commit hash %s, got %s" , firstCommit .String (), meta1 ["commit" ])
352+ }
353+
354+ data , err := os .ReadFile (clonedRepositoryPath + "/README" )
355+ if err != nil {
356+ t .Fatalf ("expected no error while reading file, got: %v" , err )
357+ }
358+
359+ if string (data ) != "v1.0 content" {
360+ t .Fatalf ("expected file content to be 'v1.0 content', got: %s" , string (data ))
361+ }
362+
363+ // Now sync to tag v2.0 using a new synchronizer
364+ ref2 := "refs/tags/v2.0"
365+ s2 := gitsync .New (clonedRepositoryPath , config.Git {
366+ Repo : testRepositoryPath ,
367+ Reference : & ref2 ,
368+ Commit : nil ,
369+ }, "test-source" )
370+
371+ meta2 , err := s2 .Execute (ctx )
372+ if err != nil {
373+ t .Fatalf ("expected no error on second execute, got %v" , err )
374+ }
375+
376+ if meta2 ["commit" ] != secondCommit .String () {
377+ t .Fatalf ("expected second commit hash %s, got %s" , secondCommit .String (), meta2 ["commit" ])
378+ }
379+
380+ data , err = os .ReadFile (clonedRepositoryPath + "/README" )
381+ if err != nil {
382+ t .Fatalf ("expected no error while reading file, got: %v" , err )
383+ }
384+
385+ if string (data ) != "v2.0 content" {
386+ t .Fatalf ("expected file content to be 'v2.0 content', got: %s" , string (data ))
387+ }
388+
389+ // Verify the tag refs were created correctly in the cloned repo
390+ clonedRepo , err := git .PlainOpen (clonedRepositoryPath )
391+ if err != nil {
392+ t .Fatalf ("expected no error opening cloned repo: %v" , err )
393+ }
394+
395+ refs , err := clonedRepo .References ()
396+ if err != nil {
397+ t .Fatalf ("expected no error getting references: %v" , err )
398+ }
399+
400+ t .Log ("Refs after tag sync:" )
401+ err = refs .ForEach (func (ref * plumbing.Reference ) error {
402+ t .Logf (" %s -> %s" , ref .Name (), ref .Hash ())
403+ return nil
404+ })
405+ if err != nil {
406+ t .Fatalf ("expected no error iterating references: %v" , err )
407+ }
408+ }
409+
117410// TestGitsyncSSH tests the functionality of the gitsync package with an SSH server.
118411// It creates a temporary git repository, commits a file, and then uses the gitsync package to clone the repository over SSH.
119412// It verifies that the cloned repository contains the expected content.
0 commit comments